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