Commit 230d1f74 authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

Merge pull request #1811 from JGoutin/msvc_update

Improve Visual C++ 14.X support
parents 297f2adc 7e1b1934
Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019.
\ No newline at end of file
......@@ -11,13 +11,17 @@ Microsoft Visual C++ 9.0:
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
Microsoft Visual C++ 14.0:
Microsoft Visual C++ 14.X:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
This may also support compilers shipped with compatible Visual Studio versions.
"""
import os
import json
from os import listdir, pathsep
from os.path import join, isfile, isdir, dirname
import sys
import platform
import itertools
......@@ -30,12 +34,9 @@ from .monkey import get_unpatched
if platform.system() == 'Windows':
from setuptools.extern.six.moves import winreg
safe_env = os.environ
from os import environ
else:
"""
Mock winreg and environ so the module can be imported
on this platform.
"""
# Mock winreg and environ so the module can be imported on this platform.
class winreg:
HKEY_USERS = None
......@@ -43,7 +44,7 @@ else:
HKEY_LOCAL_MACHINE = None
HKEY_CLASSES_ROOT = None
safe_env = dict()
environ = dict()
_msvc9_suppress_errors = (
# msvc9compiler isn't available on some platforms
......@@ -63,15 +64,13 @@ except _msvc9_suppress_errors:
def msvc9_find_vcvarsall(version):
"""
Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
compiler build for Python (VCForPython). Fall back to original behavior
when the standalone compiler is not available.
compiler build for Python
(VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
Redirect the path of "vcvarsall.bat".
Fall back to original behavior when the standalone compiler is not
available.
Known supported compilers
-------------------------
Microsoft Visual C++ 9.0:
Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
Redirect the path of "vcvarsall.bat".
Parameters
----------
......@@ -80,24 +79,25 @@ def msvc9_find_vcvarsall(version):
Return
------
vcvarsall.bat path: str
str
vcvarsall.bat path
"""
VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
key = VC_BASE % ('', version)
vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
key = vc_base % ('', version)
try:
# Per-user installs register the compiler path here
productdir = Reg.get_value(key, "installdir")
except KeyError:
try:
# All-user installs on a 64-bit system register here
key = VC_BASE % ('Wow6432Node\\', version)
key = vc_base % ('Wow6432Node\\', version)
productdir = Reg.get_value(key, "installdir")
except KeyError:
productdir = None
if productdir:
vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
if os.path.isfile(vcvarsall):
vcvarsall = join(productdir, "vcvarsall.bat")
if isfile(vcvarsall):
return vcvarsall
return get_unpatched(msvc9_find_vcvarsall)(version)
......@@ -106,20 +106,10 @@ def msvc9_find_vcvarsall(version):
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
"""
Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
compilers.
Microsoft Visual C++ 9.0 and 10.0 compilers.
Set environment without use of "vcvarsall.bat".
Known supported compilers
-------------------------
Microsoft Visual C++ 9.0:
Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
Microsoft Windows SDK 6.1 (x86, x64, ia64)
Microsoft Windows SDK 7.0 (x86, x64, ia64)
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
Parameters
----------
ver: float
......@@ -129,9 +119,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
Return
------
environment: dict
dict
environment
"""
# Try to get environement from vcvarsall.bat (Classical way)
# Try to get environment from vcvarsall.bat (Classical way)
try:
orig = get_unpatched(msvc9_query_vcvarsall)
return orig(ver, arch, *args, **kwargs)
......@@ -153,17 +144,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
def msvc14_get_vc_env(plat_spec):
"""
Patched "distutils._msvccompiler._get_vc_env" for support extra
compilers.
Microsoft Visual C++ 14.X compilers.
Set environment without use of "vcvarsall.bat".
Known supported compilers
-------------------------
Microsoft Visual C++ 14.0:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
Parameters
----------
plat_spec: str
......@@ -171,7 +155,8 @@ def msvc14_get_vc_env(plat_spec):
Return
------
environment: dict
dict
environment
"""
# Try to get environment from vcvarsall.bat (Classical way)
try:
......@@ -217,9 +202,9 @@ def _augment_exception(exc, version, arch=''):
if version == 9.0:
if arch.lower().find('ia64') > -1:
# For VC++ 9.0, if IA64 support is needed, redirect user
# to Windows SDK 7.0
message += ' Get it with "Microsoft Windows SDK 7.0": '
message += msdownload % 3138
# to Windows SDK 7.0.
# Note: No download link available from Microsoft.
message += ' Get it with "Microsoft Windows SDK 7.0"'
else:
# For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
# This redirection link is maintained by Microsoft.
......@@ -230,8 +215,8 @@ def _augment_exception(exc, version, arch=''):
message += ' Get it with "Microsoft Windows SDK 7.1": '
message += msdownload % 8279
elif version >= 14.0:
# For VC++ 14.0 Redirect user to Visual C++ Build Tools
message += (' Get it with "Microsoft Visual C++ Build Tools": '
# For VC++ 14.X Redirect user to latest Visual C++ Build Tools
message += (' Get it with "Build Tools for Visual Studio": '
r'https://visualstudio.microsoft.com/downloads/')
exc.args = (message, )
......@@ -239,26 +224,50 @@ def _augment_exception(exc, version, arch=''):
class PlatformInfo:
"""
Current and Target Architectures informations.
Current and Target Architectures information.
Parameters
----------
arch: str
Target architecture.
"""
current_cpu = safe_env.get('processor_architecture', '').lower()
current_cpu = environ.get('processor_architecture', '').lower()
def __init__(self, arch):
self.arch = arch.lower().replace('x64', 'amd64')
@property
def target_cpu(self):
"""
Return Target CPU architecture.
Return
------
str
Target CPU
"""
return self.arch[self.arch.find('_') + 1:]
def target_is_x86(self):
"""
Return True if target CPU is x86 32 bits..
Return
------
bool
CPU is x86 32 bits
"""
return self.target_cpu == 'x86'
def current_is_x86(self):
"""
Return True if current CPU is x86 32 bits..
Return
------
bool
CPU is x86 32 bits
"""
return self.current_cpu == 'x86'
def current_dir(self, hidex86=False, x64=False):
......@@ -274,8 +283,8 @@ class PlatformInfo:
Return
------
subfolder: str
'\target', or '' (see hidex86 parameter)
str
subfolder: '\target', or '' (see hidex86 parameter)
"""
return (
'' if (self.current_cpu == 'x86' and hidex86) else
......@@ -296,8 +305,8 @@ class PlatformInfo:
Return
------
subfolder: str
'\current', or '' (see hidex86 parameter)
str
subfolder: '\current', or '' (see hidex86 parameter)
"""
return (
'' if (self.target_cpu == 'x86' and hidex86) else
......@@ -312,13 +321,13 @@ class PlatformInfo:
Parameters
----------
forcex86: bool
Use 'x86' as current architecture even if current acritecture is
Use 'x86' as current architecture even if current architecture is
not x86.
Return
------
subfolder: str
'' if target architecture is current architecture,
str
subfolder: '' if target architecture is current architecture,
'\current_target' if not.
"""
current = 'x86' if forcex86 else self.current_cpu
......@@ -330,7 +339,7 @@ class PlatformInfo:
class RegistryInfo:
"""
Microsoft Visual Studio related registry informations.
Microsoft Visual Studio related registry information.
Parameters
----------
......@@ -349,6 +358,11 @@ class RegistryInfo:
def visualstudio(self):
"""
Microsoft Visual Studio root registry key.
Return
------
str
Registry key
"""
return 'VisualStudio'
......@@ -356,27 +370,47 @@ class RegistryInfo:
def sxs(self):
"""
Microsoft Visual Studio SxS registry key.
Return
------
str
Registry key
"""
return os.path.join(self.visualstudio, 'SxS')
return join(self.visualstudio, 'SxS')
@property
def vc(self):
"""
Microsoft Visual C++ VC7 registry key.
Return
------
str
Registry key
"""
return os.path.join(self.sxs, 'VC7')
return join(self.sxs, 'VC7')
@property
def vs(self):
"""
Microsoft Visual Studio VS7 registry key.
Return
------
str
Registry key
"""
return os.path.join(self.sxs, 'VS7')
return join(self.sxs, 'VS7')
@property
def vc_for_python(self):
"""
Microsoft Visual C++ for Python registry key.
Return
------
str
Registry key
"""
return r'DevDiv\VCForPython'
......@@ -384,6 +418,11 @@ class RegistryInfo:
def microsoft_sdk(self):
"""
Microsoft SDK registry key.
Return
------
str
Registry key
"""
return 'Microsoft SDKs'
......@@ -391,20 +430,35 @@ class RegistryInfo:
def windows_sdk(self):
"""
Microsoft Windows/Platform SDK registry key.
Return
------
str
Registry key
"""
return os.path.join(self.microsoft_sdk, 'Windows')
return join(self.microsoft_sdk, 'Windows')
@property
def netfx_sdk(self):
"""
Microsoft .NET Framework SDK registry key.
Return
------
str
Registry key
"""
return os.path.join(self.microsoft_sdk, 'NETFXSDK')
return join(self.microsoft_sdk, 'NETFXSDK')
@property
def windows_kits_roots(self):
"""
Microsoft Windows Kits Roots registry key.
Return
------
str
Registry key
"""
return r'Windows Kits\Installed Roots'
......@@ -421,10 +475,11 @@ class RegistryInfo:
Return
------
str: value
str
Registry key
"""
node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
return os.path.join('Software', node64, 'Microsoft', key)
return join('Software', node64, 'Microsoft', key)
def lookup(self, key, name):
"""
......@@ -439,18 +494,19 @@ class RegistryInfo:
Return
------
str: value
str
value
"""
KEY_READ = winreg.KEY_READ
key_read = winreg.KEY_READ
openkey = winreg.OpenKey
ms = self.microsoft
for hkey in self.HKEYS:
try:
bkey = openkey(hkey, ms(key), 0, KEY_READ)
bkey = openkey(hkey, ms(key), 0, key_read)
except (OSError, IOError):
if not self.pi.current_is_x86():
try:
bkey = openkey(hkey, ms(key, True), 0, KEY_READ)
bkey = openkey(hkey, ms(key, True), 0, key_read)
except (OSError, IOError):
continue
else:
......@@ -463,7 +519,7 @@ class RegistryInfo:
class SystemInfo:
"""
Microsoft Windows and Visual Studio related system inormations.
Microsoft Windows and Visual Studio related system information.
Parameters
----------
......@@ -474,30 +530,52 @@ class SystemInfo:
"""
# Variables and properties in this class use originals CamelCase variables
# names from Microsoft source files for more easy comparaison.
WinDir = safe_env.get('WinDir', '')
ProgramFiles = safe_env.get('ProgramFiles', '')
ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles)
# names from Microsoft source files for more easy comparison.
WinDir = environ.get('WinDir', '')
ProgramFiles = environ.get('ProgramFiles', '')
ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
def __init__(self, registry_info, vc_ver=None):
self.ri = registry_info
self.pi = self.ri.pi
self.vc_ver = vc_ver or self._find_latest_available_vc_ver()
def _find_latest_available_vc_ver(self):
try:
return self.find_available_vc_vers()[-1]
except IndexError:
err = 'No Microsoft Visual C++ version found'
raise distutils.errors.DistutilsPlatformError(err)
self.known_vs_paths = self.find_programdata_vs_vers()
# 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_available_vc_vers(self):
def find_reg_vs_vers(self):
"""
Find all available Microsoft Visual C++ versions.
Find Microsoft Visual Studio versions available in registry.
Return
------
list of float
Versions
"""
ms = self.ri.microsoft
vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
vc_vers = []
vs_vers = []
for hkey in self.ri.HKEYS:
for key in vckeys:
try:
......@@ -508,49 +586,108 @@ class SystemInfo:
for i in range(values):
try:
ver = float(winreg.EnumValue(bkey, i)[0])
if ver not in vc_vers:
vc_vers.append(ver)
if ver not in vs_vers:
vs_vers.append(ver)
except ValueError:
pass
for i in range(subkeys):
try:
ver = float(winreg.EnumKey(bkey, i))
if ver not in vc_vers:
vc_vers.append(ver)
if ver not in vs_vers:
vs_vers.append(ver)
except ValueError:
pass
return sorted(vc_vers)
return sorted(vs_vers)
def find_programdata_vs_vers(self):
r"""
Find Visual studio 2017+ versions from information in
"C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
Return
------
dict
float version as key, path as value.
"""
vs_versions = {}
instances_dir = \
r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
try:
hashed_names = listdir(instances_dir)
except (OSError, IOError):
# Directory not exists with all Visual Studio versions
return vs_versions
for name in hashed_names:
try:
# Get VS installation path from "state.json" file
state_path = join(instances_dir, name, 'state.json')
with open(state_path, 'rt', encoding='utf-8') as state_file:
state = json.load(state_file)
vs_path = state['installationPath']
# Raises OSError if this VS installation does not contain VC
listdir(join(vs_path, r'VC\Tools\MSVC'))
# Store version and path
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
@staticmethod
def _as_float_version(version):
"""
Return a string version as a simplified float version (major.minor)
Parameters
----------
version: str
Version.
Return
------
float
version
"""
return float('.'.join(version.split('.')[:2]))
@property
def VSInstallDir(self):
"""
Microsoft Visual Studio directory.
Return
------
str
path
"""
# Default path
name = 'Microsoft Visual Studio %0.1f' % self.vc_ver
default = os.path.join(self.ProgramFilesx86, name)
default = join(self.ProgramFilesx86,
'Microsoft Visual Studio %0.1f' % self.vs_ver)
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default
return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
@property
def VCInstallDir(self):
"""
Microsoft Visual C++ directory.
"""
self.VSInstallDir
guess_vc = self._guess_vc() or self._guess_vc_legacy()
# Try to get "VC++ for Python" path from registry as default path
reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
python_vc = self.ri.lookup(reg_path, 'installdir')
default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc
# Try to get path from registry, if fail use default path
path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc
Return
------
str
path
"""
path = self._guess_vc() or self._guess_vc_legacy()
if not os.path.isdir(path):
if not isdir(path):
msg = 'Microsoft Visual C++ directory not found'
raise distutils.errors.DistutilsPlatformError(msg)
......@@ -558,186 +695,256 @@ class SystemInfo:
def _guess_vc(self):
"""
Locate Visual C for 2017
Locate Visual C++ for VS2017+.
Return
------
str
path
"""
if self.vc_ver <= 14.0:
return
if self.vs_ver <= 14.0:
return ''
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')
default = r'VC\Tools\MSVC'
guess_vc = os.path.join(self.VSInstallDir, default)
# Subdir with VC exact version as name
try:
vc_exact_ver = os.listdir(guess_vc)[-1]
return os.path.join(guess_vc, vc_exact_ver)
# Update the VC version with real one instead of VS version
vc_ver = listdir(guess_vc)[-1]
self.vc_ver = self._as_float_version(vc_ver)
return join(guess_vc, vc_ver)
except (OSError, IOError, IndexError):
pass
return ''
def _guess_vc_legacy(self):
"""
Locate Visual C for versions prior to 2017
Locate Visual C++ for versions prior to 2017.
Return
------
str
path
"""
default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
return os.path.join(self.ProgramFilesx86, default)
default = join(self.ProgramFilesx86,
r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
# Try to get "VC++ for Python" path from registry as default path
reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
python_vc = self.ri.lookup(reg_path, 'installdir')
default_vc = join(python_vc, 'VC') if python_vc else default
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
@property
def WindowsSdkVersion(self):
"""
Microsoft Windows SDK versions for specified MSVC++ version.
"""
if self.vc_ver <= 9.0:
return ('7.0', '6.1', '6.0a')
elif self.vc_ver == 10.0:
return ('7.1', '7.0a')
elif self.vc_ver == 11.0:
return ('8.0', '8.0a')
elif self.vc_ver == 12.0:
return ('8.1', '8.1a')
elif self.vc_ver >= 14.0:
return ('10.0', '8.1')
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'
@property
def WindowsSdkLastVersion(self):
"""
Microsoft Windows SDK last version
Microsoft Windows SDK last version.
Return
------
str
version
"""
return self._use_last_dir_name(os.path.join(
self.WindowsSdkDir, 'lib'))
return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
@property
def WindowsSdkDir(self):
"""
Microsoft Windows SDK directory.
Return
------
str
path
"""
sdkdir = ''
for ver in self.WindowsSdkVersion:
# Try to get it from registry
loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver)
loc = join(self.ri.windows_sdk, 'v%s' % ver)
sdkdir = self.ri.lookup(loc, 'installationfolder')
if sdkdir:
break
if not sdkdir or not os.path.isdir(sdkdir):
if not sdkdir or not isdir(sdkdir):
# Try to get "VC++ for Python" version from registry
path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
install_base = self.ri.lookup(path, 'installdir')
if install_base:
sdkdir = os.path.join(install_base, 'WinSDK')
if not sdkdir or not os.path.isdir(sdkdir):
sdkdir = join(install_base, 'WinSDK')
if not sdkdir or not isdir(sdkdir):
# If fail, use default new path
for ver in self.WindowsSdkVersion:
intver = ver[:ver.rfind('.')]
path = r'Microsoft SDKs\Windows Kits\%s' % (intver)
d = os.path.join(self.ProgramFiles, path)
if os.path.isdir(d):
path = r'Microsoft SDKs\Windows Kits\%s' % intver
d = join(self.ProgramFiles, path)
if isdir(d):
sdkdir = d
if not sdkdir or not os.path.isdir(sdkdir):
if not sdkdir or not isdir(sdkdir):
# If fail, use default old path
for ver in self.WindowsSdkVersion:
path = r'Microsoft SDKs\Windows\v%s' % ver
d = os.path.join(self.ProgramFiles, path)
if os.path.isdir(d):
d = join(self.ProgramFiles, path)
if isdir(d):
sdkdir = d
if not sdkdir:
# If fail, use Platform SDK
sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
sdkdir = join(self.VCInstallDir, 'PlatformSDK')
return sdkdir
@property
def WindowsSDKExecutablePath(self):
"""
Microsoft Windows SDK executable directory.
Return
------
str
path
"""
# Find WinSDK NetFx Tools registry dir name
if self.vc_ver <= 11.0:
if self.vs_ver <= 11.0:
netfxver = 35
arch = ''
else:
netfxver = 40
hidex86 = True if self.vc_ver <= 12.0 else False
hidex86 = True if self.vs_ver <= 12.0 else False
arch = self.pi.current_dir(x64=True, hidex86=hidex86)
fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
# liste all possibles registry paths
# list all possibles registry paths
regpaths = []
if self.vc_ver >= 14.0:
if self.vs_ver >= 14.0:
for ver in self.NetFxSdkVersion:
regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
regpaths += [join(self.ri.netfx_sdk, ver, fx)]
for ver in self.WindowsSdkVersion:
regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
# Return installation folder from the more recent path
for path in regpaths:
execpath = self.ri.lookup(path, 'installationfolder')
if execpath:
break
return execpath
return execpath
@property
def FSharpInstallDir(self):
"""
Microsoft Visual F# directory.
Return
------
str
path
"""
path = r'%0.1f\Setup\F#' % self.vc_ver
path = os.path.join(self.ri.visualstudio, path)
path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
return self.ri.lookup(path, 'productdir') or ''
@property
def UniversalCRTSdkDir(self):
"""
Microsoft Universal CRT SDK directory.
Return
------
str
path
"""
# Set Kit Roots versions for specified MSVC++ version
if self.vc_ver >= 14.0:
vers = ('10', '81')
else:
vers = ()
vers = ('10', '81') if self.vs_ver >= 14.0 else ()
# Find path of the more recent Kit
for ver in vers:
sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
'kitsroot%s' % ver)
if sdkdir:
break
return sdkdir or ''
return sdkdir or ''
@property
def UniversalCRTSdkLastVersion(self):
"""
Microsoft Universal C Runtime SDK last version
Microsoft Universal C Runtime SDK last version.
Return
------
str
version
"""
return self._use_last_dir_name(os.path.join(
self.UniversalCRTSdkDir, 'lib'))
return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
@property
def NetFxSdkVersion(self):
"""
Microsoft .NET Framework SDK versions.
Return
------
tuple of str
versions
"""
# Set FxSdk versions for specified MSVC++ version
if self.vc_ver >= 14.0:
return ('4.6.1', '4.6')
else:
return ()
# Set FxSdk versions for specified VS version
return (('4.7.2', '4.7.1', '4.7',
'4.6.2', '4.6.1', '4.6',
'4.5.2', '4.5.1', '4.5')
if self.vs_ver >= 14.0 else ())
@property
def NetFxSdkDir(self):
"""
Microsoft .NET Framework SDK directory.
Return
------
str
path
"""
sdkdir = ''
for ver in self.NetFxSdkVersion:
loc = os.path.join(self.ri.netfx_sdk, ver)
loc = join(self.ri.netfx_sdk, ver)
sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
if sdkdir:
break
return sdkdir or ''
return sdkdir
@property
def FrameworkDir32(self):
"""
Microsoft .NET Framework 32bit directory.
Return
------
str
path
"""
# Default path
guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
......@@ -746,9 +953,14 @@ class SystemInfo:
def FrameworkDir64(self):
"""
Microsoft .NET Framework 64bit directory.
Return
------
str
path
"""
# Default path
guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
......@@ -757,6 +969,11 @@ class SystemInfo:
def FrameworkVersion32(self):
"""
Microsoft .NET Framework 32bit versions.
Return
------
tuple of str
versions
"""
return self._find_dot_net_versions(32)
......@@ -764,6 +981,11 @@ class SystemInfo:
def FrameworkVersion64(self):
"""
Microsoft .NET Framework 64bit versions.
Return
------
tuple of str
versions
"""
return self._find_dot_net_versions(64)
......@@ -775,6 +997,11 @@ class SystemInfo:
----------
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)
......@@ -782,18 +1009,17 @@ class SystemInfo:
ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
# Set .NET versions for specified MSVC++ version
if self.vc_ver >= 12.0:
frameworkver = (ver, 'v4.0')
elif self.vc_ver >= 10.0:
frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver,
'v3.5')
elif self.vc_ver == 9.0:
frameworkver = ('v3.5', 'v2.0.50727')
if self.vc_ver == 8.0:
frameworkver = ('v3.0', 'v2.0.50727')
return frameworkver
def _use_last_dir_name(self, path, prefix=''):
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'
@staticmethod
def _use_last_dir_name(path, prefix=''):
"""
Return name of the last dir in path or '' if no dir found.
......@@ -802,12 +1028,17 @@ class SystemInfo:
path: str
Use dirs in this path
prefix: str
Use only dirs startings by this prefix
Use only dirs starting by this prefix
Return
------
str
name
"""
matching_dirs = (
dir_name
for dir_name in reversed(os.listdir(path))
if os.path.isdir(os.path.join(path, dir_name)) and
for dir_name in reversed(listdir(path))
if isdir(join(path, dir_name)) and
dir_name.startswith(prefix)
)
return next(matching_dirs, None) or ''
......@@ -818,7 +1049,7 @@ 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.0.
This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
Script created by analysing Microsoft environment configuration files like
"vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
......@@ -835,7 +1066,7 @@ class EnvironmentInfo:
"""
# Variables and properties in this class use originals CamelCase variables
# names from Microsoft source files for more easy comparaison.
# names from Microsoft source files for more easy comparison.
def __init__(self, arch, vc_ver=None, vc_min_ver=0):
self.pi = PlatformInfo(arch)
......@@ -846,205 +1077,255 @@ class EnvironmentInfo:
err = 'No suitable Microsoft Visual C++ version found'
raise distutils.errors.DistutilsPlatformError(err)
@property
def vs_ver(self):
"""
Microsoft Visual Studio.
Return
------
float
version
"""
return self.si.vs_ver
@property
def vc_ver(self):
"""
Microsoft Visual C++ version.
Return
------
float
version
"""
return self.si.vc_ver
@property
def VSTools(self):
"""
Microsoft Visual Studio Tools
Microsoft Visual Studio Tools.
Return
------
list of str
paths
"""
paths = [r'Common7\IDE', r'Common7\Tools']
if self.vc_ver >= 14.0:
if self.vs_ver >= 14.0:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
paths += [r'Team Tools\Performance Tools']
paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
return [os.path.join(self.si.VSInstallDir, path) for path in paths]
return [join(self.si.VSInstallDir, path) for path in paths]
@property
def VCIncludes(self):
"""
Microsoft Visual C++ & Microsoft Foundation Class Includes
Microsoft Visual C++ & Microsoft Foundation Class Includes.
Return
------
list of str
paths
"""
return [os.path.join(self.si.VCInstallDir, 'Include'),
os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')]
return [join(self.si.VCInstallDir, 'Include'),
join(self.si.VCInstallDir, r'ATLMFC\Include')]
@property
def VCLibraries(self):
"""
Microsoft Visual C++ & Microsoft Foundation Class Libraries
Microsoft Visual C++ & Microsoft Foundation Class Libraries.
Return
------
list of str
paths
"""
if self.vc_ver >= 15.0:
if self.vs_ver >= 15.0:
arch_subdir = self.pi.target_dir(x64=True)
else:
arch_subdir = self.pi.target_dir(hidex86=True)
paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
if self.vc_ver >= 14.0:
if self.vs_ver >= 14.0:
paths += [r'Lib\store%s' % arch_subdir]
return [os.path.join(self.si.VCInstallDir, path) for path in paths]
return [join(self.si.VCInstallDir, path) for path in paths]
@property
def VCStoreRefs(self):
"""
Microsoft Visual C++ store references Libraries
Microsoft Visual C++ store references Libraries.
Return
------
list of str
paths
"""
if self.vc_ver < 14.0:
if self.vs_ver < 14.0:
return []
return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
return [join(self.si.VCInstallDir, r'Lib\store\references')]
@property
def VCTools(self):
"""
Microsoft Visual C++ Tools
Microsoft Visual C++ Tools.
Return
------
list of str
paths
"""
si = self.si
tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
tools = [join(si.VCInstallDir, 'VCPackages')]
forcex86 = True if self.vc_ver <= 10.0 else False
forcex86 = True if self.vs_ver <= 10.0 else False
arch_subdir = self.pi.cross_dir(forcex86)
if arch_subdir:
tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
if self.vc_ver == 14.0:
if self.vs_ver == 14.0:
path = 'Bin%s' % self.pi.current_dir(hidex86=True)
tools += [os.path.join(si.VCInstallDir, path)]
tools += [join(si.VCInstallDir, path)]
elif self.vc_ver >= 15.0:
elif self.vs_ver >= 15.0:
host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
r'bin\HostX64%s')
tools += [os.path.join(
tools += [join(
si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
if self.pi.current_cpu != self.pi.target_cpu:
tools += [os.path.join(
tools += [join(
si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
else:
tools += [os.path.join(si.VCInstallDir, 'Bin')]
tools += [join(si.VCInstallDir, 'Bin')]
return tools
@property
def OSLibraries(self):
"""
Microsoft Windows SDK Libraries
Microsoft Windows SDK Libraries.
Return
------
list of str
paths
"""
if self.vc_ver <= 10.0:
if self.vs_ver <= 10.0:
arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
else:
arch_subdir = self.pi.target_dir(x64=True)
lib = os.path.join(self.si.WindowsSdkDir, 'lib')
lib = join(self.si.WindowsSdkDir, 'lib')
libver = self._sdk_subdir
return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
return [join(lib, '%sum%s' % (libver , arch_subdir))]
@property
def OSIncludes(self):
"""
Microsoft Windows SDK Include
Microsoft Windows SDK Include.
Return
------
list of str
paths
"""
include = os.path.join(self.si.WindowsSdkDir, 'include')
include = join(self.si.WindowsSdkDir, 'include')
if self.vc_ver <= 10.0:
return [include, os.path.join(include, 'gl')]
if self.vs_ver <= 10.0:
return [include, join(include, 'gl')]
else:
if self.vc_ver >= 14.0:
if self.vs_ver >= 14.0:
sdkver = self._sdk_subdir
else:
sdkver = ''
return [os.path.join(include, '%sshared' % sdkver),
os.path.join(include, '%sum' % sdkver),
os.path.join(include, '%swinrt' % sdkver)]
return [join(include, '%sshared' % sdkver),
join(include, '%sum' % sdkver),
join(include, '%swinrt' % sdkver)]
@property
def OSLibpath(self):
"""
Microsoft Windows SDK Libraries Paths
Microsoft Windows SDK Libraries Paths.
Return
------
list of str
paths
"""
ref = os.path.join(self.si.WindowsSdkDir, 'References')
ref = join(self.si.WindowsSdkDir, 'References')
libpath = []
if self.vc_ver <= 9.0:
if self.vs_ver <= 9.0:
libpath += self.OSLibraries
if self.vc_ver >= 11.0:
libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
if self.vs_ver >= 11.0:
libpath += [join(ref, r'CommonConfiguration\Neutral')]
if self.vc_ver >= 14.0:
if self.vs_ver >= 14.0:
libpath += [
ref,
os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
os.path.join(
ref,
'Windows.Foundation.UniversalApiContract',
'1.0.0.0',
),
os.path.join(
ref,
'Windows.Foundation.FoundationContract',
'1.0.0.0',
),
os.path.join(
ref,
'Windows.Networking.Connectivity.WwanContract',
'1.0.0.0',
),
os.path.join(
self.si.WindowsSdkDir,
'ExtensionSDKs',
'Microsoft.VCLibs',
'%0.1f' % self.vc_ver,
'References',
'CommonConfiguration',
'neutral',
),
join(self.si.WindowsSdkDir, 'UnionMetadata'),
join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
join(ref,'Windows.Networking.Connectivity.WwanContract',
'1.0.0.0'),
join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
'%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
'neutral'),
]
return libpath
@property
def SdkTools(self):
"""
Microsoft Windows SDK Tools
Microsoft Windows SDK Tools.
Return
------
list of str
paths
"""
return list(self._sdk_tools())
def _sdk_tools(self):
"""
Microsoft Windows SDK Tools paths generator
Microsoft Windows SDK Tools paths generator.
Return
------
generator of str
paths
"""
if self.vc_ver < 15.0:
bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
yield os.path.join(self.si.WindowsSdkDir, bin_dir)
if self.vs_ver < 15.0:
bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
yield join(self.si.WindowsSdkDir, bin_dir)
if not self.pi.current_is_x86():
arch_subdir = self.pi.current_dir(x64=True)
path = 'Bin%s' % arch_subdir
yield os.path.join(self.si.WindowsSdkDir, path)
yield join(self.si.WindowsSdkDir, path)
if self.vc_ver == 10.0 or self.vc_ver == 11.0:
if self.vs_ver in (10.0, 11.0):
if self.pi.target_is_x86():
arch_subdir = ''
else:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
yield os.path.join(self.si.WindowsSdkDir, path)
yield join(self.si.WindowsSdkDir, path)
elif self.vc_ver >= 15.0:
path = os.path.join(self.si.WindowsSdkDir, 'Bin')
elif self.vs_ver >= 15.0:
path = join(self.si.WindowsSdkDir, 'Bin')
arch_subdir = self.pi.current_dir(x64=True)
sdkver = self.si.WindowsSdkLastVersion
yield os.path.join(path, '%s%s' % (sdkver, arch_subdir))
yield join(path, '%s%s' % (sdkver, arch_subdir))
if self.si.WindowsSDKExecutablePath:
yield self.si.WindowsSDKExecutablePath
......@@ -1052,7 +1333,12 @@ class EnvironmentInfo:
@property
def _sdk_subdir(self):
"""
Microsoft Windows SDK version subdir
Microsoft Windows SDK version subdir.
Return
------
str
subdir
"""
ucrtver = self.si.WindowsSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
......@@ -1060,22 +1346,32 @@ class EnvironmentInfo:
@property
def SdkSetup(self):
"""
Microsoft Windows SDK Setup
Microsoft Windows SDK Setup.
Return
------
list of str
paths
"""
if self.vc_ver > 9.0:
if self.vs_ver > 9.0:
return []
return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
return [join(self.si.WindowsSdkDir, 'Setup')]
@property
def FxTools(self):
"""
Microsoft .NET Framework Tools
Microsoft .NET Framework Tools.
Return
------
list of str
paths
"""
pi = self.pi
si = self.si
if self.vc_ver <= 10.0:
if self.vs_ver <= 10.0:
include32 = True
include64 = not pi.target_is_x86() and not pi.current_is_x86()
else:
......@@ -1084,102 +1380,142 @@ class EnvironmentInfo:
tools = []
if include32:
tools += [os.path.join(si.FrameworkDir32, ver)
tools += [join(si.FrameworkDir32, ver)
for ver in si.FrameworkVersion32]
if include64:
tools += [os.path.join(si.FrameworkDir64, ver)
tools += [join(si.FrameworkDir64, ver)
for ver in si.FrameworkVersion64]
return tools
@property
def NetFxSDKLibraries(self):
"""
Microsoft .Net Framework SDK Libraries
Microsoft .Net Framework SDK Libraries.
Return
------
list of str
paths
"""
if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
arch_subdir = self.pi.target_dir(x64=True)
return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
@property
def NetFxSDKIncludes(self):
"""
Microsoft .Net Framework SDK Includes
Microsoft .Net Framework SDK Includes.
Return
------
list of str
paths
"""
if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
return [join(self.si.NetFxSdkDir, r'include\um')]
@property
def VsTDb(self):
"""
Microsoft Visual Studio Team System Database
Microsoft Visual Studio Team System Database.
Return
------
list of str
paths
"""
return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
@property
def MSBuild(self):
"""
Microsoft Build Engine
Microsoft Build Engine.
Return
------
list of str
paths
"""
if self.vc_ver < 12.0:
if self.vs_ver < 12.0:
return []
elif self.vc_ver < 15.0:
elif self.vs_ver < 15.0:
base_path = self.si.ProgramFilesx86
arch_subdir = self.pi.current_dir(hidex86=True)
else:
base_path = self.si.VSInstallDir
arch_subdir = ''
path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
build = [os.path.join(base_path, path)]
path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
build = [join(base_path, path)]
if self.vc_ver >= 15.0:
if self.vs_ver >= 15.0:
# Add Roslyn C# & Visual Basic Compiler
build += [os.path.join(base_path, path, 'Roslyn')]
build += [join(base_path, path, 'Roslyn')]
return build
@property
def HTMLHelpWorkshop(self):
"""
Microsoft HTML Help Workshop
Microsoft HTML Help Workshop.
Return
------
list of str
paths
"""
if self.vc_ver < 11.0:
if self.vs_ver < 11.0:
return []
return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
@property
def UCRTLibraries(self):
"""
Microsoft Universal C Runtime SDK Libraries
Microsoft Universal C Runtime SDK Libraries.
Return
------
list of str
paths
"""
if self.vc_ver < 14.0:
if self.vs_ver < 14.0:
return []
arch_subdir = self.pi.target_dir(x64=True)
lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
lib = join(self.si.UniversalCRTSdkDir, 'lib')
ucrtver = self._ucrt_subdir
return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
@property
def UCRTIncludes(self):
"""
Microsoft Universal C Runtime SDK Include
Microsoft Universal C Runtime SDK Include.
Return
------
list of str
paths
"""
if self.vc_ver < 14.0:
if self.vs_ver < 14.0:
return []
include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
include = join(self.si.UniversalCRTSdkDir, 'include')
return [join(include, '%sucrt' % self._ucrt_subdir)]
@property
def _ucrt_subdir(self):
"""
Microsoft Universal C Runtime SDK version subdir
Microsoft Universal C Runtime SDK version subdir.
Return
------
str
subdir
"""
ucrtver = self.si.UniversalCRTSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
......@@ -1187,31 +1523,52 @@ class EnvironmentInfo:
@property
def FSharp(self):
"""
Microsoft Visual F#
Microsoft Visual F#.
Return
------
list of str
paths
"""
if self.vc_ver < 11.0 and self.vc_ver > 12.0:
if 11.0 > self.vs_ver > 12.0:
return []
return self.si.FSharpInstallDir
return [self.si.FSharpInstallDir]
@property
def VCRuntimeRedist(self):
"""
Microsoft Visual C++ runtime redistribuable dll
"""
arch_subdir = self.pi.target_dir(x64=True)
if self.vc_ver < 15:
redist_path = self.si.VCInstallDir
vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
else:
redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist')
vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
# Visual Studio 2017 is still Visual C++ 14.0
dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver
Microsoft Visual C++ runtime redistributable dll.
vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
return os.path.join(redist_path, vcruntime)
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):
"""
......@@ -1221,6 +1578,11 @@ class EnvironmentInfo:
----------
exists: bool
It True, only return existing paths.
Return
------
dict
environment
"""
env = dict(
include=self._build_paths('include',
......@@ -1254,7 +1616,7 @@ class EnvironmentInfo:
self.FSharp],
exists),
)
if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist):
if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
env['py_vcruntime_redist'] = self.VCRuntimeRedist
return env
......@@ -1265,20 +1627,35 @@ class EnvironmentInfo:
unique, extant, directories from those paths and from
the environment variable. Raise an error if no paths
are resolved.
Parameters
----------
name: str
Environment variable name
spec_path_lists: list of str
Paths
exists: bool
It True, only return existing paths.
Return
------
str
Pathsep-separated paths
"""
# flatten spec_path_lists
spec_paths = itertools.chain.from_iterable(spec_path_lists)
env_paths = safe_env.get(name, '').split(os.pathsep)
env_paths = environ.get(name, '').split(pathsep)
paths = itertools.chain(spec_paths, env_paths)
extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
extant_paths = list(filter(isdir, paths)) if exists else paths
if not extant_paths:
msg = "%s environment variable is empty" % name.upper()
raise distutils.errors.DistutilsPlatformError(msg)
unique_paths = self._unique_everseen(extant_paths)
return os.pathsep.join(unique_paths)
return pathsep.join(unique_paths)
# from Python docs
def _unique_everseen(self, iterable, key=None):
@staticmethod
def _unique_everseen(iterable, key=None):
"""
List unique elements, preserving order.
Remember all elements ever seen.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment