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 json 15from io import open 16from os import listdir, pathsep 17from os.path import join, isfile, isdir, dirname 18import sys 19import contextlib 20import platform 21import itertools 22import subprocess 23import distutils.errors 24from setuptools.extern.packaging.version import LegacyVersion 25from setuptools.extern.more_itertools import unique_everseen 26 27from .monkey import get_unpatched 28 29if platform.system() == 'Windows': 30 import winreg 31 from os import environ 32else: 33 # Mock winreg and environ so the module can be imported on this platform. 34 35 class winreg: 36 HKEY_USERS = None 37 HKEY_CURRENT_USER = None 38 HKEY_LOCAL_MACHINE = None 39 HKEY_CLASSES_ROOT = None 40 41 environ = dict() 42 43 44def _msvc14_find_vc2015(): 45 """Python 3.8 "distutils/_msvccompiler.py" backport""" 46 try: 47 key = winreg.OpenKey( 48 winreg.HKEY_LOCAL_MACHINE, 49 r"Software\Microsoft\VisualStudio\SxS\VC7", 50 0, 51 winreg.KEY_READ | winreg.KEY_WOW64_32KEY 52 ) 53 except OSError: 54 return None, None 55 56 best_version = 0 57 best_dir = None 58 with key: 59 for i in itertools.count(): 60 try: 61 v, vc_dir, vt = winreg.EnumValue(key, i) 62 except OSError: 63 break 64 if v and vt == winreg.REG_SZ and isdir(vc_dir): 65 try: 66 version = int(float(v)) 67 except (ValueError, TypeError): 68 continue 69 if version >= 14 and version > best_version: 70 best_version, best_dir = version, vc_dir 71 return best_version, best_dir 72 73 74def _msvc14_find_vc2017(): 75 """Python 3.8 "distutils/_msvccompiler.py" backport 76 77 Returns "15, path" based on the result of invoking vswhere.exe 78 If no install is found, returns "None, None" 79 80 The version is returned to avoid unnecessarily changing the function 81 result. It may be ignored when the path is not None. 82 83 If vswhere.exe is not available, by definition, VS 2017 is not 84 installed. 85 """ 86 root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles") 87 if not root: 88 return None, None 89 90 try: 91 path = subprocess.check_output([ 92 join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), 93 "-latest", 94 "-prerelease", 95 "-requiresAny", 96 "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", 97 "-requires", "Microsoft.VisualStudio.Workload.WDExpress", 98 "-property", "installationPath", 99 "-products", "*", 100 ]).decode(encoding="mbcs", errors="strict").strip() 101 except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): 102 return None, None 103 104 path = join(path, "VC", "Auxiliary", "Build") 105 if isdir(path): 106 return 15, path 107 108 return None, None 109 110 111PLAT_SPEC_TO_RUNTIME = { 112 'x86': 'x86', 113 'x86_amd64': 'x64', 114 'x86_arm': 'arm', 115 'x86_arm64': 'arm64' 116} 117 118 119def _msvc14_find_vcvarsall(plat_spec): 120 """Python 3.8 "distutils/_msvccompiler.py" backport""" 121 _, best_dir = _msvc14_find_vc2017() 122 vcruntime = None 123 124 if plat_spec in PLAT_SPEC_TO_RUNTIME: 125 vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec] 126 else: 127 vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' 128 129 if best_dir: 130 vcredist = join(best_dir, "..", "..", "redist", "MSVC", "**", 131 vcruntime_plat, "Microsoft.VC14*.CRT", 132 "vcruntime140.dll") 133 try: 134 import glob 135 vcruntime = glob.glob(vcredist, recursive=True)[-1] 136 except (ImportError, OSError, LookupError): 137 vcruntime = None 138 139 if not best_dir: 140 best_version, best_dir = _msvc14_find_vc2015() 141 if best_version: 142 vcruntime = join(best_dir, 'redist', vcruntime_plat, 143 "Microsoft.VC140.CRT", "vcruntime140.dll") 144 145 if not best_dir: 146 return None, None 147 148 vcvarsall = join(best_dir, "vcvarsall.bat") 149 if not isfile(vcvarsall): 150 return None, None 151 152 if not vcruntime or not isfile(vcruntime): 153 vcruntime = None 154 155 return vcvarsall, vcruntime 156 157 158def _msvc14_get_vc_env(plat_spec): 159 """Python 3.8 "distutils/_msvccompiler.py" backport""" 160 if "DISTUTILS_USE_SDK" in environ: 161 return { 162 key.lower(): value 163 for key, value in environ.items() 164 } 165 166 vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec) 167 if not vcvarsall: 168 raise distutils.errors.DistutilsPlatformError( 169 "Unable to find vcvarsall.bat" 170 ) 171 172 try: 173 out = subprocess.check_output( 174 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), 175 stderr=subprocess.STDOUT, 176 ).decode('utf-16le', errors='replace') 177 except subprocess.CalledProcessError as exc: 178 raise distutils.errors.DistutilsPlatformError( 179 "Error executing {}".format(exc.cmd) 180 ) from exc 181 182 env = { 183 key.lower(): value 184 for key, _, value in 185 (line.partition('=') for line in out.splitlines()) 186 if key and value 187 } 188 189 if vcruntime: 190 env['py_vcruntime_redist'] = vcruntime 191 return env 192 193 194def msvc14_get_vc_env(plat_spec): 195 """ 196 Patched "distutils._msvccompiler._get_vc_env" for support extra 197 Microsoft Visual C++ 14.X compilers. 198 199 Set environment without use of "vcvarsall.bat". 200 201 Parameters 202 ---------- 203 plat_spec: str 204 Target architecture. 205 206 Return 207 ------ 208 dict 209 environment 210 """ 211 212 # Always use backport from CPython 3.8 213 try: 214 return _msvc14_get_vc_env(plat_spec) 215 except distutils.errors.DistutilsPlatformError as exc: 216 _augment_exception(exc, 14.0) 217 raise 218 219 220def msvc14_gen_lib_options(*args, **kwargs): 221 """ 222 Patched "distutils._msvccompiler.gen_lib_options" for fix 223 compatibility between "numpy.distutils" and "distutils._msvccompiler" 224 (for Numpy < 1.11.2) 225 """ 226 if "numpy.distutils" in sys.modules: 227 import numpy as np 228 if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): 229 return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) 230 return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) 231 232 233def _augment_exception(exc, version, arch=''): 234 """ 235 Add details to the exception message to help guide the user 236 as to what action will resolve it. 237 """ 238 # Error if MSVC++ directory not found or environment not set 239 message = exc.args[0] 240 241 if "vcvarsall" in message.lower() or "visual c" in message.lower(): 242 # Special error message if MSVC++ not installed 243 tmpl = 'Microsoft Visual C++ {version:0.1f} or greater is required.' 244 message = tmpl.format(**locals()) 245 msdownload = 'www.microsoft.com/download/details.aspx?id=%d' 246 if version == 9.0: 247 if arch.lower().find('ia64') > -1: 248 # For VC++ 9.0, if IA64 support is needed, redirect user 249 # to Windows SDK 7.0. 250 # Note: No download link available from Microsoft. 251 message += ' Get it with "Microsoft Windows SDK 7.0"' 252 else: 253 # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : 254 # This redirection link is maintained by Microsoft. 255 # Contact vspython@microsoft.com if it needs updating. 256 message += ' Get it from http://aka.ms/vcpython27' 257 elif version == 10.0: 258 # For VC++ 10.0 Redirect user to Windows SDK 7.1 259 message += ' Get it with "Microsoft Windows SDK 7.1": ' 260 message += msdownload % 8279 261 elif version >= 14.0: 262 # For VC++ 14.X Redirect user to latest Visual C++ Build Tools 263 message += (' Get it with "Microsoft C++ Build Tools": ' 264 r'https://visualstudio.microsoft.com' 265 r'/visual-cpp-build-tools/') 266 267 exc.args = (message, ) 268 269 270class PlatformInfo: 271 """ 272 Current and Target Architectures information. 273 274 Parameters 275 ---------- 276 arch: str 277 Target architecture. 278 """ 279 current_cpu = environ.get('processor_architecture', '').lower() 280 281 def __init__(self, arch): 282 self.arch = arch.lower().replace('x64', 'amd64') 283 284 @property 285 def target_cpu(self): 286 """ 287 Return Target CPU architecture. 288 289 Return 290 ------ 291 str 292 Target CPU 293 """ 294 return self.arch[self.arch.find('_') + 1:] 295 296 def target_is_x86(self): 297 """ 298 Return True if target CPU is x86 32 bits.. 299 300 Return 301 ------ 302 bool 303 CPU is x86 32 bits 304 """ 305 return self.target_cpu == 'x86' 306 307 def current_is_x86(self): 308 """ 309 Return True if current CPU is x86 32 bits.. 310 311 Return 312 ------ 313 bool 314 CPU is x86 32 bits 315 """ 316 return self.current_cpu == 'x86' 317 318 def current_dir(self, hidex86=False, x64=False): 319 """ 320 Current platform specific subfolder. 321 322 Parameters 323 ---------- 324 hidex86: bool 325 return '' and not '\x86' if architecture is x86. 326 x64: bool 327 return '\x64' and not '\amd64' if architecture is amd64. 328 329 Return 330 ------ 331 str 332 subfolder: '\target', or '' (see hidex86 parameter) 333 """ 334 return ( 335 '' if (self.current_cpu == 'x86' and hidex86) else 336 r'\x64' if (self.current_cpu == 'amd64' and x64) else 337 r'\%s' % self.current_cpu 338 ) 339 340 def target_dir(self, hidex86=False, x64=False): 341 r""" 342 Target platform specific subfolder. 343 344 Parameters 345 ---------- 346 hidex86: bool 347 return '' and not '\x86' if architecture is x86. 348 x64: bool 349 return '\x64' and not '\amd64' if architecture is amd64. 350 351 Return 352 ------ 353 str 354 subfolder: '\current', or '' (see hidex86 parameter) 355 """ 356 return ( 357 '' if (self.target_cpu == 'x86' and hidex86) else 358 r'\x64' if (self.target_cpu == 'amd64' and x64) else 359 r'\%s' % self.target_cpu 360 ) 361 362 def cross_dir(self, forcex86=False): 363 r""" 364 Cross platform specific subfolder. 365 366 Parameters 367 ---------- 368 forcex86: bool 369 Use 'x86' as current architecture even if current architecture is 370 not x86. 371 372 Return 373 ------ 374 str 375 subfolder: '' if target architecture is current architecture, 376 '\current_target' if not. 377 """ 378 current = 'x86' if forcex86 else self.current_cpu 379 return ( 380 '' if self.target_cpu == current else 381 self.target_dir().replace('\\', '\\%s_' % current) 382 ) 383 384 385class RegistryInfo: 386 """ 387 Microsoft Visual Studio related registry information. 388 389 Parameters 390 ---------- 391 platform_info: PlatformInfo 392 "PlatformInfo" instance. 393 """ 394 HKEYS = (winreg.HKEY_USERS, 395 winreg.HKEY_CURRENT_USER, 396 winreg.HKEY_LOCAL_MACHINE, 397 winreg.HKEY_CLASSES_ROOT) 398 399 def __init__(self, platform_info): 400 self.pi = platform_info 401 402 @property 403 def visualstudio(self): 404 """ 405 Microsoft Visual Studio root registry key. 406 407 Return 408 ------ 409 str 410 Registry key 411 """ 412 return 'VisualStudio' 413 414 @property 415 def sxs(self): 416 """ 417 Microsoft Visual Studio SxS registry key. 418 419 Return 420 ------ 421 str 422 Registry key 423 """ 424 return join(self.visualstudio, 'SxS') 425 426 @property 427 def vc(self): 428 """ 429 Microsoft Visual C++ VC7 registry key. 430 431 Return 432 ------ 433 str 434 Registry key 435 """ 436 return join(self.sxs, 'VC7') 437 438 @property 439 def vs(self): 440 """ 441 Microsoft Visual Studio VS7 registry key. 442 443 Return 444 ------ 445 str 446 Registry key 447 """ 448 return join(self.sxs, 'VS7') 449 450 @property 451 def vc_for_python(self): 452 """ 453 Microsoft Visual C++ for Python registry key. 454 455 Return 456 ------ 457 str 458 Registry key 459 """ 460 return r'DevDiv\VCForPython' 461 462 @property 463 def microsoft_sdk(self): 464 """ 465 Microsoft SDK registry key. 466 467 Return 468 ------ 469 str 470 Registry key 471 """ 472 return 'Microsoft SDKs' 473 474 @property 475 def windows_sdk(self): 476 """ 477 Microsoft Windows/Platform SDK registry key. 478 479 Return 480 ------ 481 str 482 Registry key 483 """ 484 return join(self.microsoft_sdk, 'Windows') 485 486 @property 487 def netfx_sdk(self): 488 """ 489 Microsoft .NET Framework SDK registry key. 490 491 Return 492 ------ 493 str 494 Registry key 495 """ 496 return join(self.microsoft_sdk, 'NETFXSDK') 497 498 @property 499 def windows_kits_roots(self): 500 """ 501 Microsoft Windows Kits Roots registry key. 502 503 Return 504 ------ 505 str 506 Registry key 507 """ 508 return r'Windows Kits\Installed Roots' 509 510 def microsoft(self, key, x86=False): 511 """ 512 Return key in Microsoft software registry. 513 514 Parameters 515 ---------- 516 key: str 517 Registry key path where look. 518 x86: str 519 Force x86 software registry. 520 521 Return 522 ------ 523 str 524 Registry key 525 """ 526 node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' 527 return join('Software', node64, 'Microsoft', key) 528 529 def lookup(self, key, name): 530 """ 531 Look for values in registry in Microsoft software registry. 532 533 Parameters 534 ---------- 535 key: str 536 Registry key path where look. 537 name: str 538 Value name to find. 539 540 Return 541 ------ 542 str 543 value 544 """ 545 key_read = winreg.KEY_READ 546 openkey = winreg.OpenKey 547 closekey = winreg.CloseKey 548 ms = self.microsoft 549 for hkey in self.HKEYS: 550 bkey = None 551 try: 552 bkey = openkey(hkey, ms(key), 0, key_read) 553 except (OSError, IOError): 554 if not self.pi.current_is_x86(): 555 try: 556 bkey = openkey(hkey, ms(key, True), 0, key_read) 557 except (OSError, IOError): 558 continue 559 else: 560 continue 561 try: 562 return winreg.QueryValueEx(bkey, name)[0] 563 except (OSError, IOError): 564 pass 565 finally: 566 if bkey: 567 closekey(bkey) 568 569 570class SystemInfo: 571 """ 572 Microsoft Windows and Visual Studio related system information. 573 574 Parameters 575 ---------- 576 registry_info: RegistryInfo 577 "RegistryInfo" instance. 578 vc_ver: float 579 Required Microsoft Visual C++ version. 580 """ 581 582 # Variables and properties in this class use originals CamelCase variables 583 # names from Microsoft source files for more easy comparison. 584 WinDir = environ.get('WinDir', '') 585 ProgramFiles = environ.get('ProgramFiles', '') 586 ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) 587 588 def __init__(self, registry_info, vc_ver=None): 589 self.ri = registry_info 590 self.pi = self.ri.pi 591 592 self.known_vs_paths = self.find_programdata_vs_vers() 593 594 # Except for VS15+, VC version is aligned with VS version 595 self.vs_ver = self.vc_ver = ( 596 vc_ver or self._find_latest_available_vs_ver()) 597 598 def _find_latest_available_vs_ver(self): 599 """ 600 Find the latest VC version 601 602 Return 603 ------ 604 float 605 version 606 """ 607 reg_vc_vers = self.find_reg_vs_vers() 608 609 if not (reg_vc_vers or self.known_vs_paths): 610 raise distutils.errors.DistutilsPlatformError( 611 'No Microsoft Visual C++ version found') 612 613 vc_vers = set(reg_vc_vers) 614 vc_vers.update(self.known_vs_paths) 615 return sorted(vc_vers)[-1] 616 617 def find_reg_vs_vers(self): 618 """ 619 Find Microsoft Visual Studio versions available in registry. 620 621 Return 622 ------ 623 list of float 624 Versions 625 """ 626 ms = self.ri.microsoft 627 vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) 628 vs_vers = [] 629 for hkey, key in itertools.product(self.ri.HKEYS, vckeys): 630 try: 631 bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) 632 except (OSError, IOError): 633 continue 634 with bkey: 635 subkeys, values, _ = winreg.QueryInfoKey(bkey) 636 for i in range(values): 637 with contextlib.suppress(ValueError): 638 ver = float(winreg.EnumValue(bkey, i)[0]) 639 if ver not in vs_vers: 640 vs_vers.append(ver) 641 for i in range(subkeys): 642 with contextlib.suppress(ValueError): 643 ver = float(winreg.EnumKey(bkey, i)) 644 if ver not in vs_vers: 645 vs_vers.append(ver) 646 return sorted(vs_vers) 647 648 def find_programdata_vs_vers(self): 649 r""" 650 Find Visual studio 2017+ versions from information in 651 "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". 652 653 Return 654 ------ 655 dict 656 float version as key, path as value. 657 """ 658 vs_versions = {} 659 instances_dir = \ 660 r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' 661 662 try: 663 hashed_names = listdir(instances_dir) 664 665 except (OSError, IOError): 666 # Directory not exists with all Visual Studio versions 667 return vs_versions 668 669 for name in hashed_names: 670 try: 671 # Get VS installation path from "state.json" file 672 state_path = join(instances_dir, name, 'state.json') 673 with open(state_path, 'rt', encoding='utf-8') as state_file: 674 state = json.load(state_file) 675 vs_path = state['installationPath'] 676 677 # Raises OSError if this VS installation does not contain VC 678 listdir(join(vs_path, r'VC\Tools\MSVC')) 679 680 # Store version and path 681 vs_versions[self._as_float_version( 682 state['installationVersion'])] = vs_path 683 684 except (OSError, IOError, KeyError): 685 # Skip if "state.json" file is missing or bad format 686 continue 687 688 return vs_versions 689 690 @staticmethod 691 def _as_float_version(version): 692 """ 693 Return a string version as a simplified float version (major.minor) 694 695 Parameters 696 ---------- 697 version: str 698 Version. 699 700 Return 701 ------ 702 float 703 version 704 """ 705 return float('.'.join(version.split('.')[:2])) 706 707 @property 708 def VSInstallDir(self): 709 """ 710 Microsoft Visual Studio directory. 711 712 Return 713 ------ 714 str 715 path 716 """ 717 # Default path 718 default = join(self.ProgramFilesx86, 719 'Microsoft Visual Studio %0.1f' % self.vs_ver) 720 721 # Try to get path from registry, if fail use default path 722 return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default 723 724 @property 725 def VCInstallDir(self): 726 """ 727 Microsoft Visual C++ directory. 728 729 Return 730 ------ 731 str 732 path 733 """ 734 path = self._guess_vc() or self._guess_vc_legacy() 735 736 if not isdir(path): 737 msg = 'Microsoft Visual C++ directory not found' 738 raise distutils.errors.DistutilsPlatformError(msg) 739 740 return path 741 742 def _guess_vc(self): 743 """ 744 Locate Visual C++ for VS2017+. 745 746 Return 747 ------ 748 str 749 path 750 """ 751 if self.vs_ver <= 14.0: 752 return '' 753 754 try: 755 # First search in known VS paths 756 vs_dir = self.known_vs_paths[self.vs_ver] 757 except KeyError: 758 # Else, search with path from registry 759 vs_dir = self.VSInstallDir 760 761 guess_vc = join(vs_dir, r'VC\Tools\MSVC') 762 763 # Subdir with VC exact version as name 764 try: 765 # Update the VC version with real one instead of VS version 766 vc_ver = listdir(guess_vc)[-1] 767 self.vc_ver = self._as_float_version(vc_ver) 768 return join(guess_vc, vc_ver) 769 except (OSError, IOError, IndexError): 770 return '' 771 772 def _guess_vc_legacy(self): 773 """ 774 Locate Visual C++ for versions prior to 2017. 775 776 Return 777 ------ 778 str 779 path 780 """ 781 default = join(self.ProgramFilesx86, 782 r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver) 783 784 # Try to get "VC++ for Python" path from registry as default path 785 reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver) 786 python_vc = self.ri.lookup(reg_path, 'installdir') 787 default_vc = join(python_vc, 'VC') if python_vc else default 788 789 # Try to get path from registry, if fail use default path 790 return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc 791 792 @property 793 def WindowsSdkVersion(self): 794 """ 795 Microsoft Windows SDK versions for specified MSVC++ version. 796 797 Return 798 ------ 799 tuple of str 800 versions 801 """ 802 if self.vs_ver <= 9.0: 803 return '7.0', '6.1', '6.0a' 804 elif self.vs_ver == 10.0: 805 return '7.1', '7.0a' 806 elif self.vs_ver == 11.0: 807 return '8.0', '8.0a' 808 elif self.vs_ver == 12.0: 809 return '8.1', '8.1a' 810 elif self.vs_ver >= 14.0: 811 return '10.0', '8.1' 812 813 @property 814 def WindowsSdkLastVersion(self): 815 """ 816 Microsoft Windows SDK last version. 817 818 Return 819 ------ 820 str 821 version 822 """ 823 return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib')) 824 825 @property # noqa: C901 826 def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME 827 """ 828 Microsoft Windows SDK directory. 829 830 Return 831 ------ 832 str 833 path 834 """ 835 sdkdir = '' 836 for ver in self.WindowsSdkVersion: 837 # Try to get it from registry 838 loc = join(self.ri.windows_sdk, 'v%s' % ver) 839 sdkdir = self.ri.lookup(loc, 'installationfolder') 840 if sdkdir: 841 break 842 if not sdkdir or not isdir(sdkdir): 843 # Try to get "VC++ for Python" version from registry 844 path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) 845 install_base = self.ri.lookup(path, 'installdir') 846 if install_base: 847 sdkdir = join(install_base, 'WinSDK') 848 if not sdkdir or not isdir(sdkdir): 849 # If fail, use default new path 850 for ver in self.WindowsSdkVersion: 851 intver = ver[:ver.rfind('.')] 852 path = r'Microsoft SDKs\Windows Kits\%s' % intver 853 d = join(self.ProgramFiles, path) 854 if isdir(d): 855 sdkdir = d 856 if not sdkdir or not isdir(sdkdir): 857 # If fail, use default old path 858 for ver in self.WindowsSdkVersion: 859 path = r'Microsoft SDKs\Windows\v%s' % ver 860 d = join(self.ProgramFiles, path) 861 if isdir(d): 862 sdkdir = d 863 if not sdkdir: 864 # If fail, use Platform SDK 865 sdkdir = join(self.VCInstallDir, 'PlatformSDK') 866 return sdkdir 867 868 @property 869 def WindowsSDKExecutablePath(self): 870 """ 871 Microsoft Windows SDK executable directory. 872 873 Return 874 ------ 875 str 876 path 877 """ 878 # Find WinSDK NetFx Tools registry dir name 879 if self.vs_ver <= 11.0: 880 netfxver = 35 881 arch = '' 882 else: 883 netfxver = 40 884 hidex86 = True if self.vs_ver <= 12.0 else False 885 arch = self.pi.current_dir(x64=True, hidex86=hidex86) 886 fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) 887 888 # list all possibles registry paths 889 regpaths = [] 890 if self.vs_ver >= 14.0: 891 for ver in self.NetFxSdkVersion: 892 regpaths += [join(self.ri.netfx_sdk, ver, fx)] 893 894 for ver in self.WindowsSdkVersion: 895 regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)] 896 897 # Return installation folder from the more recent path 898 for path in regpaths: 899 execpath = self.ri.lookup(path, 'installationfolder') 900 if execpath: 901 return execpath 902 903 @property 904 def FSharpInstallDir(self): 905 """ 906 Microsoft Visual F# directory. 907 908 Return 909 ------ 910 str 911 path 912 """ 913 path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver) 914 return self.ri.lookup(path, 'productdir') or '' 915 916 @property 917 def UniversalCRTSdkDir(self): 918 """ 919 Microsoft Universal CRT SDK directory. 920 921 Return 922 ------ 923 str 924 path 925 """ 926 # Set Kit Roots versions for specified MSVC++ version 927 vers = ('10', '81') if self.vs_ver >= 14.0 else () 928 929 # Find path of the more recent Kit 930 for ver in vers: 931 sdkdir = self.ri.lookup(self.ri.windows_kits_roots, 932 'kitsroot%s' % ver) 933 if sdkdir: 934 return sdkdir or '' 935 936 @property 937 def UniversalCRTSdkLastVersion(self): 938 """ 939 Microsoft Universal C Runtime SDK last version. 940 941 Return 942 ------ 943 str 944 version 945 """ 946 return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib')) 947 948 @property 949 def NetFxSdkVersion(self): 950 """ 951 Microsoft .NET Framework SDK versions. 952 953 Return 954 ------ 955 tuple of str 956 versions 957 """ 958 # Set FxSdk versions for specified VS version 959 return (('4.7.2', '4.7.1', '4.7', 960 '4.6.2', '4.6.1', '4.6', 961 '4.5.2', '4.5.1', '4.5') 962 if self.vs_ver >= 14.0 else ()) 963 964 @property 965 def NetFxSdkDir(self): 966 """ 967 Microsoft .NET Framework SDK directory. 968 969 Return 970 ------ 971 str 972 path 973 """ 974 sdkdir = '' 975 for ver in self.NetFxSdkVersion: 976 loc = join(self.ri.netfx_sdk, ver) 977 sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') 978 if sdkdir: 979 break 980 return sdkdir 981 982 @property 983 def FrameworkDir32(self): 984 """ 985 Microsoft .NET Framework 32bit directory. 986 987 Return 988 ------ 989 str 990 path 991 """ 992 # Default path 993 guess_fw = join(self.WinDir, r'Microsoft.NET\Framework') 994 995 # Try to get path from registry, if fail use default path 996 return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw 997 998 @property 999 def FrameworkDir64(self): 1000 """ 1001 Microsoft .NET Framework 64bit directory. 1002 1003 Return 1004 ------ 1005 str 1006 path 1007 """ 1008 # Default path 1009 guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64') 1010 1011 # Try to get path from registry, if fail use default path 1012 return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw 1013 1014 @property 1015 def FrameworkVersion32(self): 1016 """ 1017 Microsoft .NET Framework 32bit versions. 1018 1019 Return 1020 ------ 1021 tuple of str 1022 versions 1023 """ 1024 return self._find_dot_net_versions(32) 1025 1026 @property 1027 def FrameworkVersion64(self): 1028 """ 1029 Microsoft .NET Framework 64bit versions. 1030 1031 Return 1032 ------ 1033 tuple of str 1034 versions 1035 """ 1036 return self._find_dot_net_versions(64) 1037 1038 def _find_dot_net_versions(self, bits): 1039 """ 1040 Find Microsoft .NET Framework versions. 1041 1042 Parameters 1043 ---------- 1044 bits: int 1045 Platform number of bits: 32 or 64. 1046 1047 Return 1048 ------ 1049 tuple of str 1050 versions 1051 """ 1052 # Find actual .NET version in registry 1053 reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) 1054 dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) 1055 ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' 1056 1057 # Set .NET versions for specified MSVC++ version 1058 if self.vs_ver >= 12.0: 1059 return ver, 'v4.0' 1060 elif self.vs_ver >= 10.0: 1061 return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' 1062 elif self.vs_ver == 9.0: 1063 return 'v3.5', 'v2.0.50727' 1064 elif self.vs_ver == 8.0: 1065 return 'v3.0', 'v2.0.50727' 1066 1067 @staticmethod 1068 def _use_last_dir_name(path, prefix=''): 1069 """ 1070 Return name of the last dir in path or '' if no dir found. 1071 1072 Parameters 1073 ---------- 1074 path: str 1075 Use dirs in this path 1076 prefix: str 1077 Use only dirs starting by this prefix 1078 1079 Return 1080 ------ 1081 str 1082 name 1083 """ 1084 matching_dirs = ( 1085 dir_name 1086 for dir_name in reversed(listdir(path)) 1087 if isdir(join(path, dir_name)) and 1088 dir_name.startswith(prefix) 1089 ) 1090 return next(matching_dirs, None) or '' 1091 1092 1093class EnvironmentInfo: 1094 """ 1095 Return environment variables for specified Microsoft Visual C++ version 1096 and platform : Lib, Include, Path and libpath. 1097 1098 This function is compatible with Microsoft Visual C++ 9.0 to 14.X. 1099 1100 Script created by analysing Microsoft environment configuration files like 1101 "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... 1102 1103 Parameters 1104 ---------- 1105 arch: str 1106 Target architecture. 1107 vc_ver: float 1108 Required Microsoft Visual C++ version. If not set, autodetect the last 1109 version. 1110 vc_min_ver: float 1111 Minimum Microsoft Visual C++ version. 1112 """ 1113 1114 # Variables and properties in this class use originals CamelCase variables 1115 # names from Microsoft source files for more easy comparison. 1116 1117 def __init__(self, arch, vc_ver=None, vc_min_ver=0): 1118 self.pi = PlatformInfo(arch) 1119 self.ri = RegistryInfo(self.pi) 1120 self.si = SystemInfo(self.ri, vc_ver) 1121 1122 if self.vc_ver < vc_min_ver: 1123 err = 'No suitable Microsoft Visual C++ version found' 1124 raise distutils.errors.DistutilsPlatformError(err) 1125 1126 @property 1127 def vs_ver(self): 1128 """ 1129 Microsoft Visual Studio. 1130 1131 Return 1132 ------ 1133 float 1134 version 1135 """ 1136 return self.si.vs_ver 1137 1138 @property 1139 def vc_ver(self): 1140 """ 1141 Microsoft Visual C++ version. 1142 1143 Return 1144 ------ 1145 float 1146 version 1147 """ 1148 return self.si.vc_ver 1149 1150 @property 1151 def VSTools(self): 1152 """ 1153 Microsoft Visual Studio Tools. 1154 1155 Return 1156 ------ 1157 list of str 1158 paths 1159 """ 1160 paths = [r'Common7\IDE', r'Common7\Tools'] 1161 1162 if self.vs_ver >= 14.0: 1163 arch_subdir = self.pi.current_dir(hidex86=True, x64=True) 1164 paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] 1165 paths += [r'Team Tools\Performance Tools'] 1166 paths += [r'Team Tools\Performance Tools%s' % arch_subdir] 1167 1168 return [join(self.si.VSInstallDir, path) for path in paths] 1169 1170 @property 1171 def VCIncludes(self): 1172 """ 1173 Microsoft Visual C++ & Microsoft Foundation Class Includes. 1174 1175 Return 1176 ------ 1177 list of str 1178 paths 1179 """ 1180 return [join(self.si.VCInstallDir, 'Include'), 1181 join(self.si.VCInstallDir, r'ATLMFC\Include')] 1182 1183 @property 1184 def VCLibraries(self): 1185 """ 1186 Microsoft Visual C++ & Microsoft Foundation Class Libraries. 1187 1188 Return 1189 ------ 1190 list of str 1191 paths 1192 """ 1193 if self.vs_ver >= 15.0: 1194 arch_subdir = self.pi.target_dir(x64=True) 1195 else: 1196 arch_subdir = self.pi.target_dir(hidex86=True) 1197 paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] 1198 1199 if self.vs_ver >= 14.0: 1200 paths += [r'Lib\store%s' % arch_subdir] 1201 1202 return [join(self.si.VCInstallDir, path) for path in paths] 1203 1204 @property 1205 def VCStoreRefs(self): 1206 """ 1207 Microsoft Visual C++ store references Libraries. 1208 1209 Return 1210 ------ 1211 list of str 1212 paths 1213 """ 1214 if self.vs_ver < 14.0: 1215 return [] 1216 return [join(self.si.VCInstallDir, r'Lib\store\references')] 1217 1218 @property 1219 def VCTools(self): 1220 """ 1221 Microsoft Visual C++ Tools. 1222 1223 Return 1224 ------ 1225 list of str 1226 paths 1227 """ 1228 si = self.si 1229 tools = [join(si.VCInstallDir, 'VCPackages')] 1230 1231 forcex86 = True if self.vs_ver <= 10.0 else False 1232 arch_subdir = self.pi.cross_dir(forcex86) 1233 if arch_subdir: 1234 tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)] 1235 1236 if self.vs_ver == 14.0: 1237 path = 'Bin%s' % self.pi.current_dir(hidex86=True) 1238 tools += [join(si.VCInstallDir, path)] 1239 1240 elif self.vs_ver >= 15.0: 1241 host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else 1242 r'bin\HostX64%s') 1243 tools += [join( 1244 si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] 1245 1246 if self.pi.current_cpu != self.pi.target_cpu: 1247 tools += [join( 1248 si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] 1249 1250 else: 1251 tools += [join(si.VCInstallDir, 'Bin')] 1252 1253 return tools 1254 1255 @property 1256 def OSLibraries(self): 1257 """ 1258 Microsoft Windows SDK Libraries. 1259 1260 Return 1261 ------ 1262 list of str 1263 paths 1264 """ 1265 if self.vs_ver <= 10.0: 1266 arch_subdir = self.pi.target_dir(hidex86=True, x64=True) 1267 return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] 1268 1269 else: 1270 arch_subdir = self.pi.target_dir(x64=True) 1271 lib = join(self.si.WindowsSdkDir, 'lib') 1272 libver = self._sdk_subdir 1273 return [join(lib, '%sum%s' % (libver, arch_subdir))] 1274 1275 @property 1276 def OSIncludes(self): 1277 """ 1278 Microsoft Windows SDK Include. 1279 1280 Return 1281 ------ 1282 list of str 1283 paths 1284 """ 1285 include = join(self.si.WindowsSdkDir, 'include') 1286 1287 if self.vs_ver <= 10.0: 1288 return [include, join(include, 'gl')] 1289 1290 else: 1291 if self.vs_ver >= 14.0: 1292 sdkver = self._sdk_subdir 1293 else: 1294 sdkver = '' 1295 return [join(include, '%sshared' % sdkver), 1296 join(include, '%sum' % sdkver), 1297 join(include, '%swinrt' % sdkver)] 1298 1299 @property 1300 def OSLibpath(self): 1301 """ 1302 Microsoft Windows SDK Libraries Paths. 1303 1304 Return 1305 ------ 1306 list of str 1307 paths 1308 """ 1309 ref = join(self.si.WindowsSdkDir, 'References') 1310 libpath = [] 1311 1312 if self.vs_ver <= 9.0: 1313 libpath += self.OSLibraries 1314 1315 if self.vs_ver >= 11.0: 1316 libpath += [join(ref, r'CommonConfiguration\Neutral')] 1317 1318 if self.vs_ver >= 14.0: 1319 libpath += [ 1320 ref, 1321 join(self.si.WindowsSdkDir, 'UnionMetadata'), 1322 join( 1323 ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), 1324 join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), 1325 join( 1326 ref, 'Windows.Networking.Connectivity.WwanContract', 1327 '1.0.0.0'), 1328 join( 1329 self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', 1330 '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', 1331 'neutral'), 1332 ] 1333 return libpath 1334 1335 @property 1336 def SdkTools(self): 1337 """ 1338 Microsoft Windows SDK Tools. 1339 1340 Return 1341 ------ 1342 list of str 1343 paths 1344 """ 1345 return list(self._sdk_tools()) 1346 1347 def _sdk_tools(self): 1348 """ 1349 Microsoft Windows SDK Tools paths generator. 1350 1351 Return 1352 ------ 1353 generator of str 1354 paths 1355 """ 1356 if self.vs_ver < 15.0: 1357 bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' 1358 yield join(self.si.WindowsSdkDir, bin_dir) 1359 1360 if not self.pi.current_is_x86(): 1361 arch_subdir = self.pi.current_dir(x64=True) 1362 path = 'Bin%s' % arch_subdir 1363 yield join(self.si.WindowsSdkDir, path) 1364 1365 if self.vs_ver in (10.0, 11.0): 1366 if self.pi.target_is_x86(): 1367 arch_subdir = '' 1368 else: 1369 arch_subdir = self.pi.current_dir(hidex86=True, x64=True) 1370 path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir 1371 yield join(self.si.WindowsSdkDir, path) 1372 1373 elif self.vs_ver >= 15.0: 1374 path = join(self.si.WindowsSdkDir, 'Bin') 1375 arch_subdir = self.pi.current_dir(x64=True) 1376 sdkver = self.si.WindowsSdkLastVersion 1377 yield join(path, '%s%s' % (sdkver, arch_subdir)) 1378 1379 if self.si.WindowsSDKExecutablePath: 1380 yield self.si.WindowsSDKExecutablePath 1381 1382 @property 1383 def _sdk_subdir(self): 1384 """ 1385 Microsoft Windows SDK version subdir. 1386 1387 Return 1388 ------ 1389 str 1390 subdir 1391 """ 1392 ucrtver = self.si.WindowsSdkLastVersion 1393 return ('%s\\' % ucrtver) if ucrtver else '' 1394 1395 @property 1396 def SdkSetup(self): 1397 """ 1398 Microsoft Windows SDK Setup. 1399 1400 Return 1401 ------ 1402 list of str 1403 paths 1404 """ 1405 if self.vs_ver > 9.0: 1406 return [] 1407 1408 return [join(self.si.WindowsSdkDir, 'Setup')] 1409 1410 @property 1411 def FxTools(self): 1412 """ 1413 Microsoft .NET Framework Tools. 1414 1415 Return 1416 ------ 1417 list of str 1418 paths 1419 """ 1420 pi = self.pi 1421 si = self.si 1422 1423 if self.vs_ver <= 10.0: 1424 include32 = True 1425 include64 = not pi.target_is_x86() and not pi.current_is_x86() 1426 else: 1427 include32 = pi.target_is_x86() or pi.current_is_x86() 1428 include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' 1429 1430 tools = [] 1431 if include32: 1432 tools += [join(si.FrameworkDir32, ver) 1433 for ver in si.FrameworkVersion32] 1434 if include64: 1435 tools += [join(si.FrameworkDir64, ver) 1436 for ver in si.FrameworkVersion64] 1437 return tools 1438 1439 @property 1440 def NetFxSDKLibraries(self): 1441 """ 1442 Microsoft .Net Framework SDK Libraries. 1443 1444 Return 1445 ------ 1446 list of str 1447 paths 1448 """ 1449 if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: 1450 return [] 1451 1452 arch_subdir = self.pi.target_dir(x64=True) 1453 return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] 1454 1455 @property 1456 def NetFxSDKIncludes(self): 1457 """ 1458 Microsoft .Net Framework SDK Includes. 1459 1460 Return 1461 ------ 1462 list of str 1463 paths 1464 """ 1465 if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: 1466 return [] 1467 1468 return [join(self.si.NetFxSdkDir, r'include\um')] 1469 1470 @property 1471 def VsTDb(self): 1472 """ 1473 Microsoft Visual Studio Team System Database. 1474 1475 Return 1476 ------ 1477 list of str 1478 paths 1479 """ 1480 return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')] 1481 1482 @property 1483 def MSBuild(self): 1484 """ 1485 Microsoft Build Engine. 1486 1487 Return 1488 ------ 1489 list of str 1490 paths 1491 """ 1492 if self.vs_ver < 12.0: 1493 return [] 1494 elif self.vs_ver < 15.0: 1495 base_path = self.si.ProgramFilesx86 1496 arch_subdir = self.pi.current_dir(hidex86=True) 1497 else: 1498 base_path = self.si.VSInstallDir 1499 arch_subdir = '' 1500 1501 path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir) 1502 build = [join(base_path, path)] 1503 1504 if self.vs_ver >= 15.0: 1505 # Add Roslyn C# & Visual Basic Compiler 1506 build += [join(base_path, path, 'Roslyn')] 1507 1508 return build 1509 1510 @property 1511 def HTMLHelpWorkshop(self): 1512 """ 1513 Microsoft HTML Help Workshop. 1514 1515 Return 1516 ------ 1517 list of str 1518 paths 1519 """ 1520 if self.vs_ver < 11.0: 1521 return [] 1522 1523 return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')] 1524 1525 @property 1526 def UCRTLibraries(self): 1527 """ 1528 Microsoft Universal C Runtime SDK Libraries. 1529 1530 Return 1531 ------ 1532 list of str 1533 paths 1534 """ 1535 if self.vs_ver < 14.0: 1536 return [] 1537 1538 arch_subdir = self.pi.target_dir(x64=True) 1539 lib = join(self.si.UniversalCRTSdkDir, 'lib') 1540 ucrtver = self._ucrt_subdir 1541 return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] 1542 1543 @property 1544 def UCRTIncludes(self): 1545 """ 1546 Microsoft Universal C Runtime SDK Include. 1547 1548 Return 1549 ------ 1550 list of str 1551 paths 1552 """ 1553 if self.vs_ver < 14.0: 1554 return [] 1555 1556 include = join(self.si.UniversalCRTSdkDir, 'include') 1557 return [join(include, '%sucrt' % self._ucrt_subdir)] 1558 1559 @property 1560 def _ucrt_subdir(self): 1561 """ 1562 Microsoft Universal C Runtime SDK version subdir. 1563 1564 Return 1565 ------ 1566 str 1567 subdir 1568 """ 1569 ucrtver = self.si.UniversalCRTSdkLastVersion 1570 return ('%s\\' % ucrtver) if ucrtver else '' 1571 1572 @property 1573 def FSharp(self): 1574 """ 1575 Microsoft Visual F#. 1576 1577 Return 1578 ------ 1579 list of str 1580 paths 1581 """ 1582 if 11.0 > self.vs_ver > 12.0: 1583 return [] 1584 1585 return [self.si.FSharpInstallDir] 1586 1587 @property 1588 def VCRuntimeRedist(self): 1589 """ 1590 Microsoft Visual C++ runtime redistributable dll. 1591 1592 Return 1593 ------ 1594 str 1595 path 1596 """ 1597 vcruntime = 'vcruntime%d0.dll' % self.vc_ver 1598 arch_subdir = self.pi.target_dir(x64=True).strip('\\') 1599 1600 # Installation prefixes candidates 1601 prefixes = [] 1602 tools_path = self.si.VCInstallDir 1603 redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist')) 1604 if isdir(redist_path): 1605 # Redist version may not be exactly the same as tools 1606 redist_path = join(redist_path, listdir(redist_path)[-1]) 1607 prefixes += [redist_path, join(redist_path, 'onecore')] 1608 1609 prefixes += [join(tools_path, 'redist')] # VS14 legacy path 1610 1611 # CRT directory 1612 crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10), 1613 # Sometime store in directory with VS version instead of VC 1614 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10)) 1615 1616 # vcruntime path 1617 for prefix, crt_dir in itertools.product(prefixes, crt_dirs): 1618 path = join(prefix, arch_subdir, crt_dir, vcruntime) 1619 if isfile(path): 1620 return path 1621 1622 def return_env(self, exists=True): 1623 """ 1624 Return environment dict. 1625 1626 Parameters 1627 ---------- 1628 exists: bool 1629 It True, only return existing paths. 1630 1631 Return 1632 ------ 1633 dict 1634 environment 1635 """ 1636 env = dict( 1637 include=self._build_paths('include', 1638 [self.VCIncludes, 1639 self.OSIncludes, 1640 self.UCRTIncludes, 1641 self.NetFxSDKIncludes], 1642 exists), 1643 lib=self._build_paths('lib', 1644 [self.VCLibraries, 1645 self.OSLibraries, 1646 self.FxTools, 1647 self.UCRTLibraries, 1648 self.NetFxSDKLibraries], 1649 exists), 1650 libpath=self._build_paths('libpath', 1651 [self.VCLibraries, 1652 self.FxTools, 1653 self.VCStoreRefs, 1654 self.OSLibpath], 1655 exists), 1656 path=self._build_paths('path', 1657 [self.VCTools, 1658 self.VSTools, 1659 self.VsTDb, 1660 self.SdkTools, 1661 self.SdkSetup, 1662 self.FxTools, 1663 self.MSBuild, 1664 self.HTMLHelpWorkshop, 1665 self.FSharp], 1666 exists), 1667 ) 1668 if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist): 1669 env['py_vcruntime_redist'] = self.VCRuntimeRedist 1670 return env 1671 1672 def _build_paths(self, name, spec_path_lists, exists): 1673 """ 1674 Given an environment variable name and specified paths, 1675 return a pathsep-separated string of paths containing 1676 unique, extant, directories from those paths and from 1677 the environment variable. Raise an error if no paths 1678 are resolved. 1679 1680 Parameters 1681 ---------- 1682 name: str 1683 Environment variable name 1684 spec_path_lists: list of str 1685 Paths 1686 exists: bool 1687 It True, only return existing paths. 1688 1689 Return 1690 ------ 1691 str 1692 Pathsep-separated paths 1693 """ 1694 # flatten spec_path_lists 1695 spec_paths = itertools.chain.from_iterable(spec_path_lists) 1696 env_paths = environ.get(name, '').split(pathsep) 1697 paths = itertools.chain(spec_paths, env_paths) 1698 extant_paths = list(filter(isdir, paths)) if exists else paths 1699 if not extant_paths: 1700 msg = "%s environment variable is empty" % name.upper() 1701 raise distutils.errors.DistutilsPlatformError(msg) 1702 unique_paths = unique_everseen(extant_paths) 1703 return pathsep.join(unique_paths) 1704