25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

1528 satır
40 KiB

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