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