msvc.py 42 KB

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