• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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