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