Spaces:
Runtime error
Runtime error
| """ | |
| Improved support for Microsoft Visual C++ compilers. | |
| Known supported compilers: | |
| -------------------------- | |
| Microsoft Visual C++ 14.X: | |
| Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) | |
| Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) | |
| Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64) | |
| This may also support compilers shipped with compatible Visual Studio versions. | |
| """ | |
| import json | |
| from io import open | |
| from os import listdir, pathsep | |
| from os.path import join, isfile, isdir, dirname | |
| import sys | |
| import contextlib | |
| import platform | |
| import itertools | |
| import subprocess | |
| import distutils.errors | |
| from setuptools.extern.packaging.version import LegacyVersion | |
| from setuptools.extern.more_itertools import unique_everseen | |
| from .monkey import get_unpatched | |
| if platform.system() == 'Windows': | |
| import winreg | |
| from os import environ | |
| else: | |
| # Mock winreg and environ so the module can be imported on this platform. | |
| class winreg: | |
| HKEY_USERS = None | |
| HKEY_CURRENT_USER = None | |
| HKEY_LOCAL_MACHINE = None | |
| HKEY_CLASSES_ROOT = None | |
| environ = dict() | |
| def _msvc14_find_vc2015(): | |
| """Python 3.8 "distutils/_msvccompiler.py" backport""" | |
| try: | |
| key = winreg.OpenKey( | |
| winreg.HKEY_LOCAL_MACHINE, | |
| r"Software\Microsoft\VisualStudio\SxS\VC7", | |
| 0, | |
| winreg.KEY_READ | winreg.KEY_WOW64_32KEY | |
| ) | |
| except OSError: | |
| return None, None | |
| best_version = 0 | |
| best_dir = None | |
| with key: | |
| for i in itertools.count(): | |
| try: | |
| v, vc_dir, vt = winreg.EnumValue(key, i) | |
| except OSError: | |
| break | |
| if v and vt == winreg.REG_SZ and isdir(vc_dir): | |
| try: | |
| version = int(float(v)) | |
| except (ValueError, TypeError): | |
| continue | |
| if version >= 14 and version > best_version: | |
| best_version, best_dir = version, vc_dir | |
| return best_version, best_dir | |
| def _msvc14_find_vc2017(): | |
| """Python 3.8 "distutils/_msvccompiler.py" backport | |
| Returns "15, path" based on the result of invoking vswhere.exe | |
| If no install is found, returns "None, None" | |
| The version is returned to avoid unnecessarily changing the function | |
| result. It may be ignored when the path is not None. | |
| If vswhere.exe is not available, by definition, VS 2017 is not | |
| installed. | |
| """ | |
| root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles") | |
| if not root: | |
| return None, None | |
| try: | |
| path = subprocess.check_output([ | |
| join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), | |
| "-latest", | |
| "-prerelease", | |
| "-requiresAny", | |
| "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", | |
| "-requires", "Microsoft.VisualStudio.Workload.WDExpress", | |
| "-property", "installationPath", | |
| "-products", "*", | |
| ]).decode(encoding="mbcs", errors="strict").strip() | |
| except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): | |
| return None, None | |
| path = join(path, "VC", "Auxiliary", "Build") | |
| if isdir(path): | |
| return 15, path | |
| return None, None | |
| PLAT_SPEC_TO_RUNTIME = { | |
| 'x86': 'x86', | |
| 'x86_amd64': 'x64', | |
| 'x86_arm': 'arm', | |
| 'x86_arm64': 'arm64' | |
| } | |
| def _msvc14_find_vcvarsall(plat_spec): | |
| """Python 3.8 "distutils/_msvccompiler.py" backport""" | |
| _, best_dir = _msvc14_find_vc2017() | |
| vcruntime = None | |
| if plat_spec in PLAT_SPEC_TO_RUNTIME: | |
| vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec] | |
| else: | |
| vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' | |
| if best_dir: | |
| vcredist = join(best_dir, "..", "..", "redist", "MSVC", "**", | |
| vcruntime_plat, "Microsoft.VC14*.CRT", | |
| "vcruntime140.dll") | |
| try: | |
| import glob | |
| vcruntime = glob.glob(vcredist, recursive=True)[-1] | |
| except (ImportError, OSError, LookupError): | |
| vcruntime = None | |
| if not best_dir: | |
| best_version, best_dir = _msvc14_find_vc2015() | |
| if best_version: | |
| vcruntime = join(best_dir, 'redist', vcruntime_plat, | |
| "Microsoft.VC140.CRT", "vcruntime140.dll") | |
| if not best_dir: | |
| return None, None | |
| vcvarsall = join(best_dir, "vcvarsall.bat") | |
| if not isfile(vcvarsall): | |
| return None, None | |
| if not vcruntime or not isfile(vcruntime): | |
| vcruntime = None | |
| return vcvarsall, vcruntime | |
| def _msvc14_get_vc_env(plat_spec): | |
| """Python 3.8 "distutils/_msvccompiler.py" backport""" | |
| if "DISTUTILS_USE_SDK" in environ: | |
| return { | |
| key.lower(): value | |
| for key, value in environ.items() | |
| } | |
| vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec) | |
| if not vcvarsall: | |
| raise distutils.errors.DistutilsPlatformError( | |
| "Unable to find vcvarsall.bat" | |
| ) | |
| try: | |
| out = subprocess.check_output( | |
| 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), | |
| stderr=subprocess.STDOUT, | |
| ).decode('utf-16le', errors='replace') | |
| except subprocess.CalledProcessError as exc: | |
| raise distutils.errors.DistutilsPlatformError( | |
| "Error executing {}".format(exc.cmd) | |
| ) from exc | |
| env = { | |
| key.lower(): value | |
| for key, _, value in | |
| (line.partition('=') for line in out.splitlines()) | |
| if key and value | |
| } | |
| if vcruntime: | |
| env['py_vcruntime_redist'] = vcruntime | |
| return env | |
| def msvc14_get_vc_env(plat_spec): | |
| """ | |
| Patched "distutils._msvccompiler._get_vc_env" for support extra | |
| Microsoft Visual C++ 14.X compilers. | |
| Set environment without use of "vcvarsall.bat". | |
| Parameters | |
| ---------- | |
| plat_spec: str | |
| Target architecture. | |
| Return | |
| ------ | |
| dict | |
| environment | |
| """ | |
| # Always use backport from CPython 3.8 | |
| try: | |
| return _msvc14_get_vc_env(plat_spec) | |
| except distutils.errors.DistutilsPlatformError as exc: | |
| _augment_exception(exc, 14.0) | |
| raise | |
| def msvc14_gen_lib_options(*args, **kwargs): | |
| """ | |
| Patched "distutils._msvccompiler.gen_lib_options" for fix | |
| compatibility between "numpy.distutils" and "distutils._msvccompiler" | |
| (for Numpy < 1.11.2) | |
| """ | |
| if "numpy.distutils" in sys.modules: | |
| import numpy as np | |
| if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): | |
| return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) | |
| return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) | |
| def _augment_exception(exc, version, arch=''): | |
| """ | |
| Add details to the exception message to help guide the user | |
| as to what action will resolve it. | |
| """ | |
| # Error if MSVC++ directory not found or environment not set | |
| message = exc.args[0] | |
| if "vcvarsall" in message.lower() or "visual c" in message.lower(): | |
| # Special error message if MSVC++ not installed | |
| tmpl = 'Microsoft Visual C++ {version:0.1f} or greater is required.' | |
| message = tmpl.format(**locals()) | |
| msdownload = 'www.microsoft.com/download/details.aspx?id=%d' | |
| if version == 9.0: | |
| if arch.lower().find('ia64') > -1: | |
| # For VC++ 9.0, if IA64 support is needed, redirect user | |
| # to Windows SDK 7.0. | |
| # Note: No download link available from Microsoft. | |
| message += ' Get it with "Microsoft Windows SDK 7.0"' | |
| else: | |
| # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : | |
| # This redirection link is maintained by Microsoft. | |
| # Contact [email protected] if it needs updating. | |
| message += ' Get it from http://aka.ms/vcpython27' | |
| elif version == 10.0: | |
| # For VC++ 10.0 Redirect user to Windows SDK 7.1 | |
| message += ' Get it with "Microsoft Windows SDK 7.1": ' | |
| message += msdownload % 8279 | |
| elif version >= 14.0: | |
| # For VC++ 14.X Redirect user to latest Visual C++ Build Tools | |
| message += (' Get it with "Microsoft C++ Build Tools": ' | |
| r'https://visualstudio.microsoft.com' | |
| r'/visual-cpp-build-tools/') | |
| exc.args = (message, ) | |
| class PlatformInfo: | |
| """ | |
| Current and Target Architectures information. | |
| Parameters | |
| ---------- | |
| arch: str | |
| Target architecture. | |
| """ | |
| current_cpu = environ.get('processor_architecture', '').lower() | |
| def __init__(self, arch): | |
| self.arch = arch.lower().replace('x64', 'amd64') | |
| def target_cpu(self): | |
| """ | |
| Return Target CPU architecture. | |
| Return | |
| ------ | |
| str | |
| Target CPU | |
| """ | |
| return self.arch[self.arch.find('_') + 1:] | |
| def target_is_x86(self): | |
| """ | |
| Return True if target CPU is x86 32 bits.. | |
| Return | |
| ------ | |
| bool | |
| CPU is x86 32 bits | |
| """ | |
| return self.target_cpu == 'x86' | |
| def current_is_x86(self): | |
| """ | |
| Return True if current CPU is x86 32 bits.. | |
| Return | |
| ------ | |
| bool | |
| CPU is x86 32 bits | |
| """ | |
| return self.current_cpu == 'x86' | |
| def current_dir(self, hidex86=False, x64=False): | |
| """ | |
| Current platform specific subfolder. | |
| Parameters | |
| ---------- | |
| hidex86: bool | |
| return '' and not '\x86' if architecture is x86. | |
| x64: bool | |
| return '\x64' and not '\amd64' if architecture is amd64. | |
| Return | |
| ------ | |
| str | |
| subfolder: '\target', or '' (see hidex86 parameter) | |
| """ | |
| return ( | |
| '' if (self.current_cpu == 'x86' and hidex86) else | |
| r'\x64' if (self.current_cpu == 'amd64' and x64) else | |
| r'\%s' % self.current_cpu | |
| ) | |
| def target_dir(self, hidex86=False, x64=False): | |
| r""" | |
| Target platform specific subfolder. | |
| Parameters | |
| ---------- | |
| hidex86: bool | |
| return '' and not '\x86' if architecture is x86. | |
| x64: bool | |
| return '\x64' and not '\amd64' if architecture is amd64. | |
| Return | |
| ------ | |
| str | |
| subfolder: '\current', or '' (see hidex86 parameter) | |
| """ | |
| return ( | |
| '' if (self.target_cpu == 'x86' and hidex86) else | |
| r'\x64' if (self.target_cpu == 'amd64' and x64) else | |
| r'\%s' % self.target_cpu | |
| ) | |
| def cross_dir(self, forcex86=False): | |
| r""" | |
| Cross platform specific subfolder. | |
| Parameters | |
| ---------- | |
| forcex86: bool | |
| Use 'x86' as current architecture even if current architecture is | |
| not x86. | |
| Return | |
| ------ | |
| str | |
| subfolder: '' if target architecture is current architecture, | |
| '\current_target' if not. | |
| """ | |
| current = 'x86' if forcex86 else self.current_cpu | |
| return ( | |
| '' if self.target_cpu == current else | |
| self.target_dir().replace('\\', '\\%s_' % current) | |
| ) | |
| class RegistryInfo: | |
| """ | |
| Microsoft Visual Studio related registry information. | |
| Parameters | |
| ---------- | |
| platform_info: PlatformInfo | |
| "PlatformInfo" instance. | |
| """ | |
| HKEYS = (winreg.HKEY_USERS, | |
| winreg.HKEY_CURRENT_USER, | |
| winreg.HKEY_LOCAL_MACHINE, | |
| winreg.HKEY_CLASSES_ROOT) | |
| def __init__(self, platform_info): | |
| self.pi = platform_info | |
| def visualstudio(self): | |
| """ | |
| Microsoft Visual Studio root registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return 'VisualStudio' | |
| def sxs(self): | |
| """ | |
| Microsoft Visual Studio SxS registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return join(self.visualstudio, 'SxS') | |
| def vc(self): | |
| """ | |
| Microsoft Visual C++ VC7 registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return join(self.sxs, 'VC7') | |
| def vs(self): | |
| """ | |
| Microsoft Visual Studio VS7 registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return join(self.sxs, 'VS7') | |
| def vc_for_python(self): | |
| """ | |
| Microsoft Visual C++ for Python registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return r'DevDiv\VCForPython' | |
| def microsoft_sdk(self): | |
| """ | |
| Microsoft SDK registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return 'Microsoft SDKs' | |
| def windows_sdk(self): | |
| """ | |
| Microsoft Windows/Platform SDK registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return join(self.microsoft_sdk, 'Windows') | |
| def netfx_sdk(self): | |
| """ | |
| Microsoft .NET Framework SDK registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return join(self.microsoft_sdk, 'NETFXSDK') | |
| def windows_kits_roots(self): | |
| """ | |
| Microsoft Windows Kits Roots registry key. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| return r'Windows Kits\Installed Roots' | |
| def microsoft(self, key, x86=False): | |
| """ | |
| Return key in Microsoft software registry. | |
| Parameters | |
| ---------- | |
| key: str | |
| Registry key path where look. | |
| x86: str | |
| Force x86 software registry. | |
| Return | |
| ------ | |
| str | |
| Registry key | |
| """ | |
| node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' | |
| return join('Software', node64, 'Microsoft', key) | |
| def lookup(self, key, name): | |
| """ | |
| Look for values in registry in Microsoft software registry. | |
| Parameters | |
| ---------- | |
| key: str | |
| Registry key path where look. | |
| name: str | |
| Value name to find. | |
| Return | |
| ------ | |
| str | |
| value | |
| """ | |
| key_read = winreg.KEY_READ | |
| openkey = winreg.OpenKey | |
| closekey = winreg.CloseKey | |
| ms = self.microsoft | |
| for hkey in self.HKEYS: | |
| bkey = None | |
| try: | |
| bkey = openkey(hkey, ms(key), 0, key_read) | |
| except (OSError, IOError): | |
| if not self.pi.current_is_x86(): | |
| try: | |
| bkey = openkey(hkey, ms(key, True), 0, key_read) | |
| except (OSError, IOError): | |
| continue | |
| else: | |
| continue | |
| try: | |
| return winreg.QueryValueEx(bkey, name)[0] | |
| except (OSError, IOError): | |
| pass | |
| finally: | |
| if bkey: | |
| closekey(bkey) | |
| class SystemInfo: | |
| """ | |
| Microsoft Windows and Visual Studio related system information. | |
| Parameters | |
| ---------- | |
| registry_info: RegistryInfo | |
| "RegistryInfo" instance. | |
| vc_ver: float | |
| Required Microsoft Visual C++ version. | |
| """ | |
| # Variables and properties in this class use originals CamelCase variables | |
| # names from Microsoft source files for more easy comparison. | |
| WinDir = environ.get('WinDir', '') | |
| ProgramFiles = environ.get('ProgramFiles', '') | |
| ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) | |
| def __init__(self, registry_info, vc_ver=None): | |
| self.ri = registry_info | |
| self.pi = self.ri.pi | |
| self.known_vs_paths = self.find_programdata_vs_vers() | |
| # Except for VS15+, VC version is aligned with VS version | |
| self.vs_ver = self.vc_ver = ( | |
| vc_ver or self._find_latest_available_vs_ver()) | |
| def _find_latest_available_vs_ver(self): | |
| """ | |
| Find the latest VC version | |
| Return | |
| ------ | |
| float | |
| version | |
| """ | |
| reg_vc_vers = self.find_reg_vs_vers() | |
| if not (reg_vc_vers or self.known_vs_paths): | |
| raise distutils.errors.DistutilsPlatformError( | |
| 'No Microsoft Visual C++ version found') | |
| vc_vers = set(reg_vc_vers) | |
| vc_vers.update(self.known_vs_paths) | |
| return sorted(vc_vers)[-1] | |
| def find_reg_vs_vers(self): | |
| """ | |
| Find Microsoft Visual Studio versions available in registry. | |
| Return | |
| ------ | |
| list of float | |
| Versions | |
| """ | |
| ms = self.ri.microsoft | |
| vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) | |
| vs_vers = [] | |
| for hkey, key in itertools.product(self.ri.HKEYS, vckeys): | |
| try: | |
| bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) | |
| except (OSError, IOError): | |
| continue | |
| with bkey: | |
| subkeys, values, _ = winreg.QueryInfoKey(bkey) | |
| for i in range(values): | |
| with contextlib.suppress(ValueError): | |
| ver = float(winreg.EnumValue(bkey, i)[0]) | |
| if ver not in vs_vers: | |
| vs_vers.append(ver) | |
| for i in range(subkeys): | |
| with contextlib.suppress(ValueError): | |
| ver = float(winreg.EnumKey(bkey, i)) | |
| if ver not in vs_vers: | |
| vs_vers.append(ver) | |
| return sorted(vs_vers) | |
| def find_programdata_vs_vers(self): | |
| r""" | |
| Find Visual studio 2017+ versions from information in | |
| "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". | |
| Return | |
| ------ | |
| dict | |
| float version as key, path as value. | |
| """ | |
| vs_versions = {} | |
| instances_dir = \ | |
| r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' | |
| try: | |
| hashed_names = listdir(instances_dir) | |
| except (OSError, IOError): | |
| # Directory not exists with all Visual Studio versions | |
| return vs_versions | |
| for name in hashed_names: | |
| try: | |
| # Get VS installation path from "state.json" file | |
| state_path = join(instances_dir, name, 'state.json') | |
| with open(state_path, 'rt', encoding='utf-8') as state_file: | |
| state = json.load(state_file) | |
| vs_path = state['installationPath'] | |
| # Raises OSError if this VS installation does not contain VC | |
| listdir(join(vs_path, r'VC\Tools\MSVC')) | |
| # Store version and path | |
| vs_versions[self._as_float_version( | |
| state['installationVersion'])] = vs_path | |
| except (OSError, IOError, KeyError): | |
| # Skip if "state.json" file is missing or bad format | |
| continue | |
| return vs_versions | |
| def _as_float_version(version): | |
| """ | |
| Return a string version as a simplified float version (major.minor) | |
| Parameters | |
| ---------- | |
| version: str | |
| Version. | |
| Return | |
| ------ | |
| float | |
| version | |
| """ | |
| return float('.'.join(version.split('.')[:2])) | |
| def VSInstallDir(self): | |
| """ | |
| Microsoft Visual Studio directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| # Default path | |
| default = join(self.ProgramFilesx86, | |
| 'Microsoft Visual Studio %0.1f' % self.vs_ver) | |
| # Try to get path from registry, if fail use default path | |
| return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default | |
| def VCInstallDir(self): | |
| """ | |
| Microsoft Visual C++ directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| path = self._guess_vc() or self._guess_vc_legacy() | |
| if not isdir(path): | |
| msg = 'Microsoft Visual C++ directory not found' | |
| raise distutils.errors.DistutilsPlatformError(msg) | |
| return path | |
| def _guess_vc(self): | |
| """ | |
| Locate Visual C++ for VS2017+. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| if self.vs_ver <= 14.0: | |
| return '' | |
| try: | |
| # First search in known VS paths | |
| vs_dir = self.known_vs_paths[self.vs_ver] | |
| except KeyError: | |
| # Else, search with path from registry | |
| vs_dir = self.VSInstallDir | |
| guess_vc = join(vs_dir, r'VC\Tools\MSVC') | |
| # Subdir with VC exact version as name | |
| try: | |
| # Update the VC version with real one instead of VS version | |
| vc_ver = listdir(guess_vc)[-1] | |
| self.vc_ver = self._as_float_version(vc_ver) | |
| return join(guess_vc, vc_ver) | |
| except (OSError, IOError, IndexError): | |
| return '' | |
| def _guess_vc_legacy(self): | |
| """ | |
| Locate Visual C++ for versions prior to 2017. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| default = join(self.ProgramFilesx86, | |
| r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver) | |
| # Try to get "VC++ for Python" path from registry as default path | |
| reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver) | |
| python_vc = self.ri.lookup(reg_path, 'installdir') | |
| default_vc = join(python_vc, 'VC') if python_vc else default | |
| # Try to get path from registry, if fail use default path | |
| return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc | |
| def WindowsSdkVersion(self): | |
| """ | |
| Microsoft Windows SDK versions for specified MSVC++ version. | |
| Return | |
| ------ | |
| tuple of str | |
| versions | |
| """ | |
| if self.vs_ver <= 9.0: | |
| return '7.0', '6.1', '6.0a' | |
| elif self.vs_ver == 10.0: | |
| return '7.1', '7.0a' | |
| elif self.vs_ver == 11.0: | |
| return '8.0', '8.0a' | |
| elif self.vs_ver == 12.0: | |
| return '8.1', '8.1a' | |
| elif self.vs_ver >= 14.0: | |
| return '10.0', '8.1' | |
| def WindowsSdkLastVersion(self): | |
| """ | |
| Microsoft Windows SDK last version. | |
| Return | |
| ------ | |
| str | |
| version | |
| """ | |
| return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib')) | |
| # noqa: C901 | |
| def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME | |
| """ | |
| Microsoft Windows SDK directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| sdkdir = '' | |
| for ver in self.WindowsSdkVersion: | |
| # Try to get it from registry | |
| loc = join(self.ri.windows_sdk, 'v%s' % ver) | |
| sdkdir = self.ri.lookup(loc, 'installationfolder') | |
| if sdkdir: | |
| break | |
| if not sdkdir or not isdir(sdkdir): | |
| # Try to get "VC++ for Python" version from registry | |
| path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) | |
| install_base = self.ri.lookup(path, 'installdir') | |
| if install_base: | |
| sdkdir = join(install_base, 'WinSDK') | |
| if not sdkdir or not isdir(sdkdir): | |
| # If fail, use default new path | |
| for ver in self.WindowsSdkVersion: | |
| intver = ver[:ver.rfind('.')] | |
| path = r'Microsoft SDKs\Windows Kits\%s' % intver | |
| d = join(self.ProgramFiles, path) | |
| if isdir(d): | |
| sdkdir = d | |
| if not sdkdir or not isdir(sdkdir): | |
| # If fail, use default old path | |
| for ver in self.WindowsSdkVersion: | |
| path = r'Microsoft SDKs\Windows\v%s' % ver | |
| d = join(self.ProgramFiles, path) | |
| if isdir(d): | |
| sdkdir = d | |
| if not sdkdir: | |
| # If fail, use Platform SDK | |
| sdkdir = join(self.VCInstallDir, 'PlatformSDK') | |
| return sdkdir | |
| def WindowsSDKExecutablePath(self): | |
| """ | |
| Microsoft Windows SDK executable directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| # Find WinSDK NetFx Tools registry dir name | |
| if self.vs_ver <= 11.0: | |
| netfxver = 35 | |
| arch = '' | |
| else: | |
| netfxver = 40 | |
| hidex86 = True if self.vs_ver <= 12.0 else False | |
| arch = self.pi.current_dir(x64=True, hidex86=hidex86) | |
| fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) | |
| # list all possibles registry paths | |
| regpaths = [] | |
| if self.vs_ver >= 14.0: | |
| for ver in self.NetFxSdkVersion: | |
| regpaths += [join(self.ri.netfx_sdk, ver, fx)] | |
| for ver in self.WindowsSdkVersion: | |
| regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)] | |
| # Return installation folder from the more recent path | |
| for path in regpaths: | |
| execpath = self.ri.lookup(path, 'installationfolder') | |
| if execpath: | |
| return execpath | |
| def FSharpInstallDir(self): | |
| """ | |
| Microsoft Visual F# directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver) | |
| return self.ri.lookup(path, 'productdir') or '' | |
| def UniversalCRTSdkDir(self): | |
| """ | |
| Microsoft Universal CRT SDK directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| # Set Kit Roots versions for specified MSVC++ version | |
| vers = ('10', '81') if self.vs_ver >= 14.0 else () | |
| # Find path of the more recent Kit | |
| for ver in vers: | |
| sdkdir = self.ri.lookup(self.ri.windows_kits_roots, | |
| 'kitsroot%s' % ver) | |
| if sdkdir: | |
| return sdkdir or '' | |
| def UniversalCRTSdkLastVersion(self): | |
| """ | |
| Microsoft Universal C Runtime SDK last version. | |
| Return | |
| ------ | |
| str | |
| version | |
| """ | |
| return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib')) | |
| def NetFxSdkVersion(self): | |
| """ | |
| Microsoft .NET Framework SDK versions. | |
| Return | |
| ------ | |
| tuple of str | |
| versions | |
| """ | |
| # Set FxSdk versions for specified VS version | |
| return (('4.7.2', '4.7.1', '4.7', | |
| '4.6.2', '4.6.1', '4.6', | |
| '4.5.2', '4.5.1', '4.5') | |
| if self.vs_ver >= 14.0 else ()) | |
| def NetFxSdkDir(self): | |
| """ | |
| Microsoft .NET Framework SDK directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| sdkdir = '' | |
| for ver in self.NetFxSdkVersion: | |
| loc = join(self.ri.netfx_sdk, ver) | |
| sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') | |
| if sdkdir: | |
| break | |
| return sdkdir | |
| def FrameworkDir32(self): | |
| """ | |
| Microsoft .NET Framework 32bit directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| # Default path | |
| guess_fw = join(self.WinDir, r'Microsoft.NET\Framework') | |
| # Try to get path from registry, if fail use default path | |
| return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw | |
| def FrameworkDir64(self): | |
| """ | |
| Microsoft .NET Framework 64bit directory. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| # Default path | |
| guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64') | |
| # Try to get path from registry, if fail use default path | |
| return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw | |
| def FrameworkVersion32(self): | |
| """ | |
| Microsoft .NET Framework 32bit versions. | |
| Return | |
| ------ | |
| tuple of str | |
| versions | |
| """ | |
| return self._find_dot_net_versions(32) | |
| def FrameworkVersion64(self): | |
| """ | |
| Microsoft .NET Framework 64bit versions. | |
| Return | |
| ------ | |
| tuple of str | |
| versions | |
| """ | |
| return self._find_dot_net_versions(64) | |
| def _find_dot_net_versions(self, bits): | |
| """ | |
| Find Microsoft .NET Framework versions. | |
| Parameters | |
| ---------- | |
| bits: int | |
| Platform number of bits: 32 or 64. | |
| Return | |
| ------ | |
| tuple of str | |
| versions | |
| """ | |
| # Find actual .NET version in registry | |
| reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) | |
| dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) | |
| ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' | |
| # Set .NET versions for specified MSVC++ version | |
| if self.vs_ver >= 12.0: | |
| return ver, 'v4.0' | |
| elif self.vs_ver >= 10.0: | |
| return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' | |
| elif self.vs_ver == 9.0: | |
| return 'v3.5', 'v2.0.50727' | |
| elif self.vs_ver == 8.0: | |
| return 'v3.0', 'v2.0.50727' | |
| def _use_last_dir_name(path, prefix=''): | |
| """ | |
| Return name of the last dir in path or '' if no dir found. | |
| Parameters | |
| ---------- | |
| path: str | |
| Use dirs in this path | |
| prefix: str | |
| Use only dirs starting by this prefix | |
| Return | |
| ------ | |
| str | |
| name | |
| """ | |
| matching_dirs = ( | |
| dir_name | |
| for dir_name in reversed(listdir(path)) | |
| if isdir(join(path, dir_name)) and | |
| dir_name.startswith(prefix) | |
| ) | |
| return next(matching_dirs, None) or '' | |
| class EnvironmentInfo: | |
| """ | |
| Return environment variables for specified Microsoft Visual C++ version | |
| and platform : Lib, Include, Path and libpath. | |
| This function is compatible with Microsoft Visual C++ 9.0 to 14.X. | |
| Script created by analysing Microsoft environment configuration files like | |
| "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... | |
| Parameters | |
| ---------- | |
| arch: str | |
| Target architecture. | |
| vc_ver: float | |
| Required Microsoft Visual C++ version. If not set, autodetect the last | |
| version. | |
| vc_min_ver: float | |
| Minimum Microsoft Visual C++ version. | |
| """ | |
| # Variables and properties in this class use originals CamelCase variables | |
| # names from Microsoft source files for more easy comparison. | |
| def __init__(self, arch, vc_ver=None, vc_min_ver=0): | |
| self.pi = PlatformInfo(arch) | |
| self.ri = RegistryInfo(self.pi) | |
| self.si = SystemInfo(self.ri, vc_ver) | |
| if self.vc_ver < vc_min_ver: | |
| err = 'No suitable Microsoft Visual C++ version found' | |
| raise distutils.errors.DistutilsPlatformError(err) | |
| def vs_ver(self): | |
| """ | |
| Microsoft Visual Studio. | |
| Return | |
| ------ | |
| float | |
| version | |
| """ | |
| return self.si.vs_ver | |
| def vc_ver(self): | |
| """ | |
| Microsoft Visual C++ version. | |
| Return | |
| ------ | |
| float | |
| version | |
| """ | |
| return self.si.vc_ver | |
| def VSTools(self): | |
| """ | |
| Microsoft Visual Studio Tools. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| paths = [r'Common7\IDE', r'Common7\Tools'] | |
| if self.vs_ver >= 14.0: | |
| arch_subdir = self.pi.current_dir(hidex86=True, x64=True) | |
| paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] | |
| paths += [r'Team Tools\Performance Tools'] | |
| paths += [r'Team Tools\Performance Tools%s' % arch_subdir] | |
| return [join(self.si.VSInstallDir, path) for path in paths] | |
| def VCIncludes(self): | |
| """ | |
| Microsoft Visual C++ & Microsoft Foundation Class Includes. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| return [join(self.si.VCInstallDir, 'Include'), | |
| join(self.si.VCInstallDir, r'ATLMFC\Include')] | |
| def VCLibraries(self): | |
| """ | |
| Microsoft Visual C++ & Microsoft Foundation Class Libraries. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver >= 15.0: | |
| arch_subdir = self.pi.target_dir(x64=True) | |
| else: | |
| arch_subdir = self.pi.target_dir(hidex86=True) | |
| paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] | |
| if self.vs_ver >= 14.0: | |
| paths += [r'Lib\store%s' % arch_subdir] | |
| return [join(self.si.VCInstallDir, path) for path in paths] | |
| def VCStoreRefs(self): | |
| """ | |
| Microsoft Visual C++ store references Libraries. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 14.0: | |
| return [] | |
| return [join(self.si.VCInstallDir, r'Lib\store\references')] | |
| def VCTools(self): | |
| """ | |
| Microsoft Visual C++ Tools. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| si = self.si | |
| tools = [join(si.VCInstallDir, 'VCPackages')] | |
| forcex86 = True if self.vs_ver <= 10.0 else False | |
| arch_subdir = self.pi.cross_dir(forcex86) | |
| if arch_subdir: | |
| tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)] | |
| if self.vs_ver == 14.0: | |
| path = 'Bin%s' % self.pi.current_dir(hidex86=True) | |
| tools += [join(si.VCInstallDir, path)] | |
| elif self.vs_ver >= 15.0: | |
| host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else | |
| r'bin\HostX64%s') | |
| tools += [join( | |
| si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] | |
| if self.pi.current_cpu != self.pi.target_cpu: | |
| tools += [join( | |
| si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] | |
| else: | |
| tools += [join(si.VCInstallDir, 'Bin')] | |
| return tools | |
| def OSLibraries(self): | |
| """ | |
| Microsoft Windows SDK Libraries. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver <= 10.0: | |
| arch_subdir = self.pi.target_dir(hidex86=True, x64=True) | |
| return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] | |
| else: | |
| arch_subdir = self.pi.target_dir(x64=True) | |
| lib = join(self.si.WindowsSdkDir, 'lib') | |
| libver = self._sdk_subdir | |
| return [join(lib, '%sum%s' % (libver, arch_subdir))] | |
| def OSIncludes(self): | |
| """ | |
| Microsoft Windows SDK Include. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| include = join(self.si.WindowsSdkDir, 'include') | |
| if self.vs_ver <= 10.0: | |
| return [include, join(include, 'gl')] | |
| else: | |
| if self.vs_ver >= 14.0: | |
| sdkver = self._sdk_subdir | |
| else: | |
| sdkver = '' | |
| return [join(include, '%sshared' % sdkver), | |
| join(include, '%sum' % sdkver), | |
| join(include, '%swinrt' % sdkver)] | |
| def OSLibpath(self): | |
| """ | |
| Microsoft Windows SDK Libraries Paths. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| ref = join(self.si.WindowsSdkDir, 'References') | |
| libpath = [] | |
| if self.vs_ver <= 9.0: | |
| libpath += self.OSLibraries | |
| if self.vs_ver >= 11.0: | |
| libpath += [join(ref, r'CommonConfiguration\Neutral')] | |
| if self.vs_ver >= 14.0: | |
| libpath += [ | |
| ref, | |
| join(self.si.WindowsSdkDir, 'UnionMetadata'), | |
| join( | |
| ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), | |
| join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), | |
| join( | |
| ref, 'Windows.Networking.Connectivity.WwanContract', | |
| '1.0.0.0'), | |
| join( | |
| self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', | |
| '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', | |
| 'neutral'), | |
| ] | |
| return libpath | |
| def SdkTools(self): | |
| """ | |
| Microsoft Windows SDK Tools. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| return list(self._sdk_tools()) | |
| def _sdk_tools(self): | |
| """ | |
| Microsoft Windows SDK Tools paths generator. | |
| Return | |
| ------ | |
| generator of str | |
| paths | |
| """ | |
| if self.vs_ver < 15.0: | |
| bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' | |
| yield join(self.si.WindowsSdkDir, bin_dir) | |
| if not self.pi.current_is_x86(): | |
| arch_subdir = self.pi.current_dir(x64=True) | |
| path = 'Bin%s' % arch_subdir | |
| yield join(self.si.WindowsSdkDir, path) | |
| if self.vs_ver in (10.0, 11.0): | |
| if self.pi.target_is_x86(): | |
| arch_subdir = '' | |
| else: | |
| arch_subdir = self.pi.current_dir(hidex86=True, x64=True) | |
| path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir | |
| yield join(self.si.WindowsSdkDir, path) | |
| elif self.vs_ver >= 15.0: | |
| path = join(self.si.WindowsSdkDir, 'Bin') | |
| arch_subdir = self.pi.current_dir(x64=True) | |
| sdkver = self.si.WindowsSdkLastVersion | |
| yield join(path, '%s%s' % (sdkver, arch_subdir)) | |
| if self.si.WindowsSDKExecutablePath: | |
| yield self.si.WindowsSDKExecutablePath | |
| def _sdk_subdir(self): | |
| """ | |
| Microsoft Windows SDK version subdir. | |
| Return | |
| ------ | |
| str | |
| subdir | |
| """ | |
| ucrtver = self.si.WindowsSdkLastVersion | |
| return ('%s\\' % ucrtver) if ucrtver else '' | |
| def SdkSetup(self): | |
| """ | |
| Microsoft Windows SDK Setup. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver > 9.0: | |
| return [] | |
| return [join(self.si.WindowsSdkDir, 'Setup')] | |
| def FxTools(self): | |
| """ | |
| Microsoft .NET Framework Tools. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| pi = self.pi | |
| si = self.si | |
| if self.vs_ver <= 10.0: | |
| include32 = True | |
| include64 = not pi.target_is_x86() and not pi.current_is_x86() | |
| else: | |
| include32 = pi.target_is_x86() or pi.current_is_x86() | |
| include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' | |
| tools = [] | |
| if include32: | |
| tools += [join(si.FrameworkDir32, ver) | |
| for ver in si.FrameworkVersion32] | |
| if include64: | |
| tools += [join(si.FrameworkDir64, ver) | |
| for ver in si.FrameworkVersion64] | |
| return tools | |
| def NetFxSDKLibraries(self): | |
| """ | |
| Microsoft .Net Framework SDK Libraries. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: | |
| return [] | |
| arch_subdir = self.pi.target_dir(x64=True) | |
| return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] | |
| def NetFxSDKIncludes(self): | |
| """ | |
| Microsoft .Net Framework SDK Includes. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: | |
| return [] | |
| return [join(self.si.NetFxSdkDir, r'include\um')] | |
| def VsTDb(self): | |
| """ | |
| Microsoft Visual Studio Team System Database. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')] | |
| def MSBuild(self): | |
| """ | |
| Microsoft Build Engine. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 12.0: | |
| return [] | |
| elif self.vs_ver < 15.0: | |
| base_path = self.si.ProgramFilesx86 | |
| arch_subdir = self.pi.current_dir(hidex86=True) | |
| else: | |
| base_path = self.si.VSInstallDir | |
| arch_subdir = '' | |
| path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir) | |
| build = [join(base_path, path)] | |
| if self.vs_ver >= 15.0: | |
| # Add Roslyn C# & Visual Basic Compiler | |
| build += [join(base_path, path, 'Roslyn')] | |
| return build | |
| def HTMLHelpWorkshop(self): | |
| """ | |
| Microsoft HTML Help Workshop. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 11.0: | |
| return [] | |
| return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')] | |
| def UCRTLibraries(self): | |
| """ | |
| Microsoft Universal C Runtime SDK Libraries. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 14.0: | |
| return [] | |
| arch_subdir = self.pi.target_dir(x64=True) | |
| lib = join(self.si.UniversalCRTSdkDir, 'lib') | |
| ucrtver = self._ucrt_subdir | |
| return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] | |
| def UCRTIncludes(self): | |
| """ | |
| Microsoft Universal C Runtime SDK Include. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if self.vs_ver < 14.0: | |
| return [] | |
| include = join(self.si.UniversalCRTSdkDir, 'include') | |
| return [join(include, '%sucrt' % self._ucrt_subdir)] | |
| def _ucrt_subdir(self): | |
| """ | |
| Microsoft Universal C Runtime SDK version subdir. | |
| Return | |
| ------ | |
| str | |
| subdir | |
| """ | |
| ucrtver = self.si.UniversalCRTSdkLastVersion | |
| return ('%s\\' % ucrtver) if ucrtver else '' | |
| def FSharp(self): | |
| """ | |
| Microsoft Visual F#. | |
| Return | |
| ------ | |
| list of str | |
| paths | |
| """ | |
| if 11.0 > self.vs_ver > 12.0: | |
| return [] | |
| return [self.si.FSharpInstallDir] | |
| def VCRuntimeRedist(self): | |
| """ | |
| Microsoft Visual C++ runtime redistributable dll. | |
| Return | |
| ------ | |
| str | |
| path | |
| """ | |
| vcruntime = 'vcruntime%d0.dll' % self.vc_ver | |
| arch_subdir = self.pi.target_dir(x64=True).strip('\\') | |
| # Installation prefixes candidates | |
| prefixes = [] | |
| tools_path = self.si.VCInstallDir | |
| redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist')) | |
| if isdir(redist_path): | |
| # Redist version may not be exactly the same as tools | |
| redist_path = join(redist_path, listdir(redist_path)[-1]) | |
| prefixes += [redist_path, join(redist_path, 'onecore')] | |
| prefixes += [join(tools_path, 'redist')] # VS14 legacy path | |
| # CRT directory | |
| crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10), | |
| # Sometime store in directory with VS version instead of VC | |
| 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10)) | |
| # vcruntime path | |
| for prefix, crt_dir in itertools.product(prefixes, crt_dirs): | |
| path = join(prefix, arch_subdir, crt_dir, vcruntime) | |
| if isfile(path): | |
| return path | |
| def return_env(self, exists=True): | |
| """ | |
| Return environment dict. | |
| Parameters | |
| ---------- | |
| exists: bool | |
| It True, only return existing paths. | |
| Return | |
| ------ | |
| dict | |
| environment | |
| """ | |
| env = dict( | |
| include=self._build_paths('include', | |
| [self.VCIncludes, | |
| self.OSIncludes, | |
| self.UCRTIncludes, | |
| self.NetFxSDKIncludes], | |
| exists), | |
| lib=self._build_paths('lib', | |
| [self.VCLibraries, | |
| self.OSLibraries, | |
| self.FxTools, | |
| self.UCRTLibraries, | |
| self.NetFxSDKLibraries], | |
| exists), | |
| libpath=self._build_paths('libpath', | |
| [self.VCLibraries, | |
| self.FxTools, | |
| self.VCStoreRefs, | |
| self.OSLibpath], | |
| exists), | |
| path=self._build_paths('path', | |
| [self.VCTools, | |
| self.VSTools, | |
| self.VsTDb, | |
| self.SdkTools, | |
| self.SdkSetup, | |
| self.FxTools, | |
| self.MSBuild, | |
| self.HTMLHelpWorkshop, | |
| self.FSharp], | |
| exists), | |
| ) | |
| if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist): | |
| env['py_vcruntime_redist'] = self.VCRuntimeRedist | |
| return env | |
| def _build_paths(self, name, spec_path_lists, exists): | |
| """ | |
| Given an environment variable name and specified paths, | |
| return a pathsep-separated string of paths containing | |
| unique, extant, directories from those paths and from | |
| the environment variable. Raise an error if no paths | |
| are resolved. | |
| Parameters | |
| ---------- | |
| name: str | |
| Environment variable name | |
| spec_path_lists: list of str | |
| Paths | |
| exists: bool | |
| It True, only return existing paths. | |
| Return | |
| ------ | |
| str | |
| Pathsep-separated paths | |
| """ | |
| # flatten spec_path_lists | |
| spec_paths = itertools.chain.from_iterable(spec_path_lists) | |
| env_paths = environ.get(name, '').split(pathsep) | |
| paths = itertools.chain(spec_paths, env_paths) | |
| extant_paths = list(filter(isdir, paths)) if exists else paths | |
| if not extant_paths: | |
| msg = "%s environment variable is empty" % name.upper() | |
| raise distutils.errors.DistutilsPlatformError(msg) | |
| unique_paths = unique_everseen(extant_paths) | |
| return pathsep.join(unique_paths) | |