msvc.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  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. When host CPU is ARM, the tools should be found for ARM.
  860. >>> getfixture('windows_only')
  861. >>> mp = getfixture('monkeypatch')
  862. >>> mp.setattr(PlatformInfo, 'current_cpu', 'arm64')
  863. >>> ei = EnvironmentInfo(arch='irrelevant')
  864. >>> paths = ei.VCTools
  865. >>> any('HostARM64' in path for path in paths)
  866. True
  867. """
  868. si = self.si
  869. tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
  870. forcex86 = True if self.vs_ver <= 10.0 else False
  871. arch_subdir = self.pi.cross_dir(forcex86)
  872. if arch_subdir:
  873. tools += [os.path.join(si.VCInstallDir, f'Bin{arch_subdir}')]
  874. if self.vs_ver == 14.0:
  875. path = f'Bin{self.pi.current_dir(hidex86=True)}'
  876. tools += [os.path.join(si.VCInstallDir, path)]
  877. elif self.vs_ver >= 15.0:
  878. host_id = self.pi.current_cpu.replace('amd64', 'x64').upper()
  879. host_dir = os.path.join('bin', f'Host{host_id}%s')
  880. tools += [
  881. os.path.join(si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))
  882. ]
  883. if self.pi.current_cpu != self.pi.target_cpu:
  884. tools += [
  885. os.path.join(
  886. si.VCInstallDir, host_dir % self.pi.current_dir(x64=True)
  887. )
  888. ]
  889. else:
  890. tools += [os.path.join(si.VCInstallDir, 'Bin')]
  891. return tools
  892. @property
  893. def OSLibraries(self):
  894. """
  895. Microsoft Windows SDK Libraries.
  896. Return
  897. ------
  898. list of str
  899. paths
  900. """
  901. if self.vs_ver <= 10.0:
  902. arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
  903. return [os.path.join(self.si.WindowsSdkDir, f'Lib{arch_subdir}')]
  904. else:
  905. arch_subdir = self.pi.target_dir(x64=True)
  906. lib = os.path.join(self.si.WindowsSdkDir, 'lib')
  907. libver = self._sdk_subdir
  908. return [os.path.join(lib, f'{libver}um{arch_subdir}')]
  909. @property
  910. def OSIncludes(self):
  911. """
  912. Microsoft Windows SDK Include.
  913. Return
  914. ------
  915. list of str
  916. paths
  917. """
  918. include = os.path.join(self.si.WindowsSdkDir, 'include')
  919. if self.vs_ver <= 10.0:
  920. return [include, os.path.join(include, 'gl')]
  921. else:
  922. if self.vs_ver >= 14.0:
  923. sdkver = self._sdk_subdir
  924. else:
  925. sdkver = ''
  926. return [
  927. os.path.join(include, f'{sdkver}shared'),
  928. os.path.join(include, f'{sdkver}um'),
  929. os.path.join(include, f'{sdkver}winrt'),
  930. ]
  931. @property
  932. def OSLibpath(self):
  933. """
  934. Microsoft Windows SDK Libraries Paths.
  935. Return
  936. ------
  937. list of str
  938. paths
  939. """
  940. ref = os.path.join(self.si.WindowsSdkDir, 'References')
  941. libpath = []
  942. if self.vs_ver <= 9.0:
  943. libpath += self.OSLibraries
  944. if self.vs_ver >= 11.0:
  945. libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
  946. if self.vs_ver >= 14.0:
  947. libpath += [
  948. ref,
  949. os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
  950. os.path.join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
  951. os.path.join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
  952. os.path.join(
  953. ref, 'Windows.Networking.Connectivity.WwanContract', '1.0.0.0'
  954. ),
  955. os.path.join(
  956. self.si.WindowsSdkDir,
  957. 'ExtensionSDKs',
  958. 'Microsoft.VCLibs',
  959. f'{self.vs_ver:0.1f}',
  960. 'References',
  961. 'CommonConfiguration',
  962. 'neutral',
  963. ),
  964. ]
  965. return libpath
  966. @property
  967. def SdkTools(self):
  968. """
  969. Microsoft Windows SDK Tools.
  970. Return
  971. ------
  972. list of str
  973. paths
  974. """
  975. return list(self._sdk_tools())
  976. def _sdk_tools(self):
  977. """
  978. Microsoft Windows SDK Tools paths generator.
  979. Return
  980. ------
  981. generator of str
  982. paths
  983. """
  984. if self.vs_ver < 15.0:
  985. bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
  986. yield os.path.join(self.si.WindowsSdkDir, bin_dir)
  987. if not self.pi.current_is_x86():
  988. arch_subdir = self.pi.current_dir(x64=True)
  989. path = f'Bin{arch_subdir}'
  990. yield os.path.join(self.si.WindowsSdkDir, path)
  991. if self.vs_ver in (10.0, 11.0):
  992. if self.pi.target_is_x86():
  993. arch_subdir = ''
  994. else:
  995. arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
  996. path = rf'Bin\NETFX 4.0 Tools{arch_subdir}'
  997. yield os.path.join(self.si.WindowsSdkDir, path)
  998. elif self.vs_ver >= 15.0:
  999. path = os.path.join(self.si.WindowsSdkDir, 'Bin')
  1000. arch_subdir = self.pi.current_dir(x64=True)
  1001. sdkver = self.si.WindowsSdkLastVersion
  1002. yield os.path.join(path, f'{sdkver}{arch_subdir}')
  1003. if self.si.WindowsSDKExecutablePath:
  1004. yield self.si.WindowsSDKExecutablePath
  1005. @property
  1006. def _sdk_subdir(self):
  1007. """
  1008. Microsoft Windows SDK version subdir.
  1009. Return
  1010. ------
  1011. str
  1012. subdir
  1013. """
  1014. ucrtver = self.si.WindowsSdkLastVersion
  1015. return (f'{ucrtver}\\') if ucrtver else ''
  1016. @property
  1017. def SdkSetup(self):
  1018. """
  1019. Microsoft Windows SDK Setup.
  1020. Return
  1021. ------
  1022. list of str
  1023. paths
  1024. """
  1025. if self.vs_ver > 9.0:
  1026. return []
  1027. return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
  1028. @property
  1029. def FxTools(self):
  1030. """
  1031. Microsoft .NET Framework Tools.
  1032. Return
  1033. ------
  1034. list of str
  1035. paths
  1036. """
  1037. pi = self.pi
  1038. si = self.si
  1039. if self.vs_ver <= 10.0:
  1040. include32 = True
  1041. include64 = not pi.target_is_x86() and not pi.current_is_x86()
  1042. else:
  1043. include32 = pi.target_is_x86() or pi.current_is_x86()
  1044. include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64'
  1045. tools = []
  1046. if include32:
  1047. tools += [
  1048. os.path.join(si.FrameworkDir32, ver) for ver in si.FrameworkVersion32
  1049. ]
  1050. if include64:
  1051. tools += [
  1052. os.path.join(si.FrameworkDir64, ver) for ver in si.FrameworkVersion64
  1053. ]
  1054. return tools
  1055. @property
  1056. def NetFxSDKLibraries(self):
  1057. """
  1058. Microsoft .Net Framework SDK Libraries.
  1059. Return
  1060. ------
  1061. list of str
  1062. paths
  1063. """
  1064. if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
  1065. return []
  1066. arch_subdir = self.pi.target_dir(x64=True)
  1067. return [os.path.join(self.si.NetFxSdkDir, rf'lib\um{arch_subdir}')]
  1068. @property
  1069. def NetFxSDKIncludes(self):
  1070. """
  1071. Microsoft .Net Framework SDK Includes.
  1072. Return
  1073. ------
  1074. list of str
  1075. paths
  1076. """
  1077. if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
  1078. return []
  1079. return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
  1080. @property
  1081. def VsTDb(self):
  1082. """
  1083. Microsoft Visual Studio Team System Database.
  1084. Return
  1085. ------
  1086. list of str
  1087. paths
  1088. """
  1089. return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
  1090. @property
  1091. def MSBuild(self):
  1092. """
  1093. Microsoft Build Engine.
  1094. Return
  1095. ------
  1096. list of str
  1097. paths
  1098. """
  1099. if self.vs_ver < 12.0:
  1100. return []
  1101. elif self.vs_ver < 15.0:
  1102. base_path = self.si.ProgramFilesx86
  1103. arch_subdir = self.pi.current_dir(hidex86=True)
  1104. else:
  1105. base_path = self.si.VSInstallDir
  1106. arch_subdir = ''
  1107. path = rf'MSBuild\{self.vs_ver:0.1f}\bin{arch_subdir}'
  1108. build = [os.path.join(base_path, path)]
  1109. if self.vs_ver >= 15.0:
  1110. # Add Roslyn C# & Visual Basic Compiler
  1111. build += [os.path.join(base_path, path, 'Roslyn')]
  1112. return build
  1113. @property
  1114. def HTMLHelpWorkshop(self):
  1115. """
  1116. Microsoft HTML Help Workshop.
  1117. Return
  1118. ------
  1119. list of str
  1120. paths
  1121. """
  1122. if self.vs_ver < 11.0:
  1123. return []
  1124. return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
  1125. @property
  1126. def UCRTLibraries(self):
  1127. """
  1128. Microsoft Universal C Runtime SDK Libraries.
  1129. Return
  1130. ------
  1131. list of str
  1132. paths
  1133. """
  1134. if self.vs_ver < 14.0:
  1135. return []
  1136. arch_subdir = self.pi.target_dir(x64=True)
  1137. lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
  1138. ucrtver = self._ucrt_subdir
  1139. return [os.path.join(lib, f'{ucrtver}ucrt{arch_subdir}')]
  1140. @property
  1141. def UCRTIncludes(self):
  1142. """
  1143. Microsoft Universal C Runtime SDK Include.
  1144. Return
  1145. ------
  1146. list of str
  1147. paths
  1148. """
  1149. if self.vs_ver < 14.0:
  1150. return []
  1151. include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
  1152. return [os.path.join(include, f'{self._ucrt_subdir}ucrt')]
  1153. @property
  1154. def _ucrt_subdir(self):
  1155. """
  1156. Microsoft Universal C Runtime SDK version subdir.
  1157. Return
  1158. ------
  1159. str
  1160. subdir
  1161. """
  1162. ucrtver = self.si.UniversalCRTSdkLastVersion
  1163. return (f'{ucrtver}\\') if ucrtver else ''
  1164. @property
  1165. def FSharp(self):
  1166. """
  1167. Microsoft Visual F#.
  1168. Return
  1169. ------
  1170. list of str
  1171. paths
  1172. """
  1173. if 11.0 > self.vs_ver > 12.0:
  1174. return []
  1175. return [self.si.FSharpInstallDir]
  1176. @property
  1177. def VCRuntimeRedist(self) -> str | None:
  1178. """
  1179. Microsoft Visual C++ runtime redistributable dll.
  1180. Returns the first suitable path found or None.
  1181. """
  1182. vcruntime = f'vcruntime{self.vc_ver}0.dll'
  1183. arch_subdir = self.pi.target_dir(x64=True).strip('\\')
  1184. # Installation prefixes candidates
  1185. prefixes = []
  1186. tools_path = self.si.VCInstallDir
  1187. redist_path = os.path.dirname(tools_path.replace(r'\Tools', r'\Redist'))
  1188. if os.path.isdir(redist_path):
  1189. # Redist version may not be exactly the same as tools
  1190. redist_path = os.path.join(redist_path, os.listdir(redist_path)[-1])
  1191. prefixes += [redist_path, os.path.join(redist_path, 'onecore')]
  1192. prefixes += [os.path.join(tools_path, 'redist')] # VS14 legacy path
  1193. # CRT directory
  1194. crt_dirs = (
  1195. f'Microsoft.VC{self.vc_ver * 10}.CRT',
  1196. # Sometime store in directory with VS version instead of VC
  1197. f'Microsoft.VC{int(self.vs_ver) * 10}.CRT',
  1198. )
  1199. # vcruntime path
  1200. candidate_paths = (
  1201. os.path.join(prefix, arch_subdir, crt_dir, vcruntime)
  1202. for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs)
  1203. )
  1204. return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682
  1205. def return_env(self, exists: bool = True) -> _EnvironmentDict:
  1206. """
  1207. Return environment dict.
  1208. Parameters
  1209. ----------
  1210. exists: bool
  1211. It True, only return existing paths.
  1212. Return
  1213. ------
  1214. dict
  1215. environment
  1216. """
  1217. env = _EnvironmentDict(
  1218. include=self._build_paths(
  1219. 'include',
  1220. [
  1221. self.VCIncludes,
  1222. self.OSIncludes,
  1223. self.UCRTIncludes,
  1224. self.NetFxSDKIncludes,
  1225. ],
  1226. exists,
  1227. ),
  1228. lib=self._build_paths(
  1229. 'lib',
  1230. [
  1231. self.VCLibraries,
  1232. self.OSLibraries,
  1233. self.FxTools,
  1234. self.UCRTLibraries,
  1235. self.NetFxSDKLibraries,
  1236. ],
  1237. exists,
  1238. ),
  1239. libpath=self._build_paths(
  1240. 'libpath',
  1241. [self.VCLibraries, self.FxTools, self.VCStoreRefs, self.OSLibpath],
  1242. exists,
  1243. ),
  1244. path=self._build_paths(
  1245. 'path',
  1246. [
  1247. self.VCTools,
  1248. self.VSTools,
  1249. self.VsTDb,
  1250. self.SdkTools,
  1251. self.SdkSetup,
  1252. self.FxTools,
  1253. self.MSBuild,
  1254. self.HTMLHelpWorkshop,
  1255. self.FSharp,
  1256. ],
  1257. exists,
  1258. ),
  1259. )
  1260. if self.vs_ver >= 14 and self.VCRuntimeRedist:
  1261. env['py_vcruntime_redist'] = self.VCRuntimeRedist
  1262. return env
  1263. def _build_paths(self, name, spec_path_lists, exists):
  1264. """
  1265. Given an environment variable name and specified paths,
  1266. return a pathsep-separated string of paths containing
  1267. unique, extant, directories from those paths and from
  1268. the environment variable. Raise an error if no paths
  1269. are resolved.
  1270. Parameters
  1271. ----------
  1272. name: str
  1273. Environment variable name
  1274. spec_path_lists: list of str
  1275. Paths
  1276. exists: bool
  1277. It True, only return existing paths.
  1278. Return
  1279. ------
  1280. str
  1281. Pathsep-separated paths
  1282. """
  1283. # flatten spec_path_lists
  1284. spec_paths = itertools.chain.from_iterable(spec_path_lists)
  1285. env_paths = environ.get(name, '').split(os.pathsep)
  1286. paths = itertools.chain(spec_paths, env_paths)
  1287. extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
  1288. if not extant_paths:
  1289. msg = f"{name.upper()} environment variable is empty"
  1290. raise distutils.errors.DistutilsPlatformError(msg)
  1291. unique_paths = unique_everseen(extant_paths)
  1292. return os.pathsep.join(unique_paths)