| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557 |
- """
- Environment info about Microsoft Compilers.
- >>> getfixture('windows_only')
- >>> ei = EnvironmentInfo('amd64')
- """
- from __future__ import annotations
- import contextlib
- import itertools
- import json
- import os
- import os.path
- import platform
- from typing import TYPE_CHECKING, TypedDict, overload
- from more_itertools import unique_everseen
- from ._path import StrPath
- from .compat import py310
- import distutils.errors
- if TYPE_CHECKING:
- from typing_extensions import LiteralString, NotRequired
- # https://github.com/python/mypy/issues/8166
- if not TYPE_CHECKING and platform.system() == 'Windows':
- import winreg
- from os import environ
- else:
- # Mock winreg and environ so the module can be imported on this platform.
- class winreg:
- HKEY_USERS = None
- HKEY_CURRENT_USER = None
- HKEY_LOCAL_MACHINE = None
- HKEY_CLASSES_ROOT = None
- environ: dict[str, str] = dict()
- class PlatformInfo:
- """
- Current and Target Architectures information.
- Parameters
- ----------
- arch: str
- Target architecture.
- """
- current_cpu = environ.get('processor_architecture', '').lower()
- def __init__(self, arch: str) -> None:
- self.arch = arch.lower().replace('x64', 'amd64')
- @property
- def target_cpu(self) -> str:
- """
- Return Target CPU architecture.
- Return
- ------
- str
- Target CPU
- """
- return self.arch[self.arch.find('_') + 1 :]
- def target_is_x86(self) -> bool:
- """
- Return True if target CPU is x86 32 bits..
- Return
- ------
- bool
- CPU is x86 32 bits
- """
- return self.target_cpu == 'x86'
- def current_is_x86(self) -> bool:
- """
- Return True if current CPU is x86 32 bits..
- Return
- ------
- bool
- CPU is x86 32 bits
- """
- return self.current_cpu == 'x86'
- def current_dir(self, hidex86=False, x64=False) -> str:
- """
- Current platform specific subfolder.
- Parameters
- ----------
- hidex86: bool
- return '' and not '\x86' if architecture is x86.
- x64: bool
- return '\x64' and not '\amd64' if architecture is amd64.
- Return
- ------
- str
- subfolder: '\target', or '' (see hidex86 parameter)
- """
- return (
- ''
- if (self.current_cpu == 'x86' and hidex86)
- else r'\x64'
- if (self.current_cpu == 'amd64' and x64)
- else rf'\{self.current_cpu}'
- )
- def target_dir(self, hidex86=False, x64=False) -> str:
- r"""
- Target platform specific subfolder.
- Parameters
- ----------
- hidex86: bool
- return '' and not '\x86' if architecture is x86.
- x64: bool
- return '\x64' and not '\amd64' if architecture is amd64.
- Return
- ------
- str
- subfolder: '\current', or '' (see hidex86 parameter)
- """
- return (
- ''
- if (self.target_cpu == 'x86' and hidex86)
- else r'\x64'
- if (self.target_cpu == 'amd64' and x64)
- else rf'\{self.target_cpu}'
- )
- def cross_dir(self, forcex86=False) -> str:
- r"""
- Cross platform specific subfolder.
- Parameters
- ----------
- forcex86: bool
- Use 'x86' as current architecture even if current architecture is
- not x86.
- Return
- ------
- str
- subfolder: '' if target architecture is current architecture,
- '\current_target' if not.
- """
- current = 'x86' if forcex86 else self.current_cpu
- return (
- ''
- if self.target_cpu == current
- else self.target_dir().replace('\\', f'\\{current}_')
- )
- class RegistryInfo:
- """
- Microsoft Visual Studio related registry information.
- Parameters
- ----------
- platform_info: PlatformInfo
- "PlatformInfo" instance.
- """
- HKEYS = (
- winreg.HKEY_USERS,
- winreg.HKEY_CURRENT_USER,
- winreg.HKEY_LOCAL_MACHINE,
- winreg.HKEY_CLASSES_ROOT,
- )
- def __init__(self, platform_info: PlatformInfo) -> None:
- self.pi = platform_info
- @property
- def visualstudio(self) -> LiteralString:
- """
- Microsoft Visual Studio root registry key.
- Return
- ------
- str
- Registry key
- """
- return 'VisualStudio'
- @property
- def sxs(self) -> LiteralString:
- """
- Microsoft Visual Studio SxS registry key.
- Return
- ------
- str
- Registry key
- """
- return os.path.join(self.visualstudio, 'SxS')
- @property
- def vc(self) -> LiteralString:
- """
- Microsoft Visual C++ VC7 registry key.
- Return
- ------
- str
- Registry key
- """
- return os.path.join(self.sxs, 'VC7')
- @property
- def vs(self) -> LiteralString:
- """
- Microsoft Visual Studio VS7 registry key.
- Return
- ------
- str
- Registry key
- """
- return os.path.join(self.sxs, 'VS7')
- @property
- def vc_for_python(self) -> LiteralString:
- """
- Microsoft Visual C++ for Python registry key.
- Return
- ------
- str
- Registry key
- """
- return r'DevDiv\VCForPython'
- @property
- def microsoft_sdk(self) -> LiteralString:
- """
- Microsoft SDK registry key.
- Return
- ------
- str
- Registry key
- """
- return 'Microsoft SDKs'
- @property
- def windows_sdk(self) -> LiteralString:
- """
- Microsoft Windows/Platform SDK registry key.
- Return
- ------
- str
- Registry key
- """
- return os.path.join(self.microsoft_sdk, 'Windows')
- @property
- def netfx_sdk(self) -> LiteralString:
- """
- Microsoft .NET Framework SDK registry key.
- Return
- ------
- str
- Registry key
- """
- return os.path.join(self.microsoft_sdk, 'NETFXSDK')
- @property
- def windows_kits_roots(self) -> LiteralString:
- """
- Microsoft Windows Kits Roots registry key.
- Return
- ------
- str
- Registry key
- """
- return r'Windows Kits\Installed Roots'
- @overload
- def microsoft(self, key: LiteralString, x86: bool = False) -> LiteralString: ...
- @overload
- def microsoft(self, key: str, x86: bool = False) -> str: ... # type: ignore[misc]
- def microsoft(self, key: str, x86: bool = False) -> str:
- """
- Return key in Microsoft software registry.
- Parameters
- ----------
- key: str
- Registry key path where look.
- x86: bool
- Force x86 software registry.
- Return
- ------
- str
- Registry key
- """
- node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
- return os.path.join('Software', node64, 'Microsoft', key)
- def lookup(self, key: str, name: str) -> str | None:
- """
- Look for values in registry in Microsoft software registry.
- Parameters
- ----------
- key: str
- Registry key path where look.
- name: str
- Value name to find.
- Return
- ------
- str | None
- value
- """
- key_read = winreg.KEY_READ
- openkey = winreg.OpenKey
- closekey = winreg.CloseKey
- ms = self.microsoft
- for hkey in self.HKEYS:
- bkey = None
- try:
- bkey = openkey(hkey, ms(key), 0, key_read)
- except OSError:
- if not self.pi.current_is_x86():
- try:
- bkey = openkey(hkey, ms(key, True), 0, key_read)
- except OSError:
- continue
- else:
- continue
- try:
- return winreg.QueryValueEx(bkey, name)[0]
- except OSError:
- pass
- finally:
- if bkey:
- closekey(bkey)
- return None
- class SystemInfo:
- """
- Microsoft Windows and Visual Studio related system information.
- Parameters
- ----------
- registry_info: RegistryInfo
- "RegistryInfo" instance.
- vc_ver: float
- Required Microsoft Visual C++ version.
- """
- # Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparison.
- WinDir = environ.get('WinDir', '')
- ProgramFiles = environ.get('ProgramFiles', '')
- ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
- def __init__(
- self, registry_info: RegistryInfo, vc_ver: float | None = None
- ) -> None:
- self.ri = registry_info
- self.pi = self.ri.pi
- self.known_vs_paths = self.find_programdata_vs_vers()
- # Except for VS15+, VC version is aligned with VS version
- self.vs_ver = self.vc_ver = vc_ver or self._find_latest_available_vs_ver()
- def _find_latest_available_vs_ver(self):
- """
- Find the latest VC version
- Return
- ------
- float
- version
- """
- reg_vc_vers = self.find_reg_vs_vers()
- if not (reg_vc_vers or self.known_vs_paths):
- raise distutils.errors.DistutilsPlatformError(
- 'No Microsoft Visual C++ version found'
- )
- vc_vers = set(reg_vc_vers)
- vc_vers.update(self.known_vs_paths)
- return max(vc_vers)
- def find_reg_vs_vers(self) -> list[float]:
- """
- Find Microsoft Visual Studio versions available in registry.
- Return
- ------
- list of float
- Versions
- """
- ms = self.ri.microsoft
- vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
- vs_vers = []
- for hkey, key in itertools.product(self.ri.HKEYS, vckeys):
- try:
- bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
- except OSError:
- continue
- with bkey:
- subkeys, values, _ = winreg.QueryInfoKey(bkey)
- for i in range(values):
- with contextlib.suppress(ValueError):
- ver = float(winreg.EnumValue(bkey, i)[0])
- if ver not in vs_vers:
- vs_vers.append(ver)
- for i in range(subkeys):
- with contextlib.suppress(ValueError):
- ver = float(winreg.EnumKey(bkey, i))
- if ver not in vs_vers:
- vs_vers.append(ver)
- return sorted(vs_vers)
- def find_programdata_vs_vers(self) -> dict[float, str]:
- r"""
- Find Visual studio 2017+ versions from information in
- "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
- Return
- ------
- dict
- float version as key, path as value.
- """
- vs_versions: dict[float, str] = {}
- instances_dir = r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
- try:
- hashed_names = os.listdir(instances_dir)
- except OSError:
- # Directory not exists with all Visual Studio versions
- return vs_versions
- for name in hashed_names:
- try:
- # Get VS installation path from "state.json" file
- state_path = os.path.join(instances_dir, name, 'state.json')
- with open(state_path, 'rt', encoding='utf-8') as state_file:
- state = json.load(state_file)
- vs_path = state['installationPath']
- # Raises OSError if this VS installation does not contain VC
- os.listdir(os.path.join(vs_path, r'VC\Tools\MSVC'))
- # Store version and path
- vs_versions[self._as_float_version(state['installationVersion'])] = (
- vs_path
- )
- except (OSError, KeyError):
- # Skip if "state.json" file is missing or bad format
- continue
- return vs_versions
- @staticmethod
- def _as_float_version(version):
- """
- Return a string version as a simplified float version (major.minor)
- Parameters
- ----------
- version: str
- Version.
- Return
- ------
- float
- version
- """
- return float('.'.join(version.split('.')[:2]))
- @property
- def VSInstallDir(self) -> str:
- """
- Microsoft Visual Studio directory.
- Return
- ------
- str
- path
- """
- # Default path
- default = os.path.join(
- self.ProgramFilesx86, f'Microsoft Visual Studio {self.vs_ver:0.1f}'
- )
- # Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vs, f'{self.vs_ver:0.1f}') or default
- @property
- def VCInstallDir(self) -> str:
- """
- Microsoft Visual C++ directory.
- Return
- ------
- str
- path
- """
- path = self._guess_vc() or self._guess_vc_legacy()
- if not os.path.isdir(path):
- msg = 'Microsoft Visual C++ directory not found'
- raise distutils.errors.DistutilsPlatformError(msg)
- return path
- def _guess_vc(self):
- """
- Locate Visual C++ for VS2017+.
- Return
- ------
- str
- path
- """
- if self.vs_ver <= 14.0:
- return ''
- try:
- # First search in known VS paths
- vs_dir = self.known_vs_paths[self.vs_ver]
- except KeyError:
- # Else, search with path from registry
- vs_dir = self.VSInstallDir
- guess_vc = os.path.join(vs_dir, r'VC\Tools\MSVC')
- # Subdir with VC exact version as name
- try:
- # Update the VC version with real one instead of VS version
- vc_ver = os.listdir(guess_vc)[-1]
- self.vc_ver = self._as_float_version(vc_ver)
- return os.path.join(guess_vc, vc_ver)
- except (OSError, IndexError):
- return ''
- def _guess_vc_legacy(self):
- """
- Locate Visual C++ for versions prior to 2017.
- Return
- ------
- str
- path
- """
- default = os.path.join(
- self.ProgramFilesx86,
- rf'Microsoft Visual Studio {self.vs_ver:0.1f}\VC',
- )
- # Try to get "VC++ for Python" path from registry as default path
- reg_path = os.path.join(self.ri.vc_for_python, f'{self.vs_ver:0.1f}')
- python_vc = self.ri.lookup(reg_path, 'installdir')
- default_vc = os.path.join(python_vc, 'VC') if python_vc else default
- # Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vc, f'{self.vs_ver:0.1f}') or default_vc
- @property
- def WindowsSdkVersion(self) -> tuple[LiteralString, ...]:
- """
- Microsoft Windows SDK versions for specified MSVC++ version.
- Return
- ------
- tuple of str
- versions
- """
- if self.vs_ver <= 9.0:
- return '7.0', '6.1', '6.0a'
- elif self.vs_ver == 10.0:
- return '7.1', '7.0a'
- elif self.vs_ver == 11.0:
- return '8.0', '8.0a'
- elif self.vs_ver == 12.0:
- return '8.1', '8.1a'
- elif self.vs_ver >= 14.0:
- return '10.0', '8.1'
- return ()
- @property
- def WindowsSdkLastVersion(self) -> str:
- """
- Microsoft Windows SDK last version.
- Return
- ------
- str
- version
- """
- return self._use_last_dir_name(os.path.join(self.WindowsSdkDir, 'lib'))
- @property
- def WindowsSdkDir(self) -> str: # noqa: C901 # is too complex (12) # FIXME
- """
- Microsoft Windows SDK directory.
- Return
- ------
- str
- path
- """
- sdkdir: str | None = ''
- for ver in self.WindowsSdkVersion:
- # Try to get it from registry
- loc = os.path.join(self.ri.windows_sdk, f'v{ver}')
- sdkdir = self.ri.lookup(loc, 'installationfolder')
- if sdkdir:
- break
- if not sdkdir or not os.path.isdir(sdkdir):
- # Try to get "VC++ for Python" version from registry
- path = os.path.join(self.ri.vc_for_python, f'{self.vc_ver:0.1f}')
- install_base = self.ri.lookup(path, 'installdir')
- if install_base:
- sdkdir = os.path.join(install_base, 'WinSDK')
- if not sdkdir or not os.path.isdir(sdkdir):
- # If fail, use default new path
- for ver in self.WindowsSdkVersion:
- intver = ver[: ver.rfind('.')]
- path = rf'Microsoft SDKs\Windows Kits\{intver}'
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
- sdkdir = d
- if not sdkdir or not os.path.isdir(sdkdir):
- # If fail, use default old path
- for ver in self.WindowsSdkVersion:
- path = rf'Microsoft SDKs\Windows\v{ver}'
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
- sdkdir = d
- if not sdkdir:
- # If fail, use Platform SDK
- sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
- return sdkdir
- @property
- def WindowsSDKExecutablePath(self) -> str | None:
- """
- Microsoft Windows SDK executable directory.
- Return
- ------
- str | None
- path
- """
- # Find WinSDK NetFx Tools registry dir name
- if self.vs_ver <= 11.0:
- netfxver = 35
- arch = ''
- else:
- netfxver = 40
- hidex86 = True if self.vs_ver <= 12.0 else False
- arch = self.pi.current_dir(x64=True, hidex86=hidex86).replace('\\', '-')
- fx = f'WinSDK-NetFx{netfxver}Tools{arch}'
- # list all possibles registry paths
- regpaths = []
- if self.vs_ver >= 14.0:
- for ver in self.NetFxSdkVersion:
- regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
- for ver in self.WindowsSdkVersion:
- regpaths += [os.path.join(self.ri.windows_sdk, f'v{ver}A', fx)]
- # Return installation folder from the more recent path
- for path in regpaths:
- execpath = self.ri.lookup(path, 'installationfolder')
- if execpath:
- return execpath
- return None
- @property
- def FSharpInstallDir(self) -> str:
- """
- Microsoft Visual F# directory.
- Return
- ------
- str
- path
- """
- path = os.path.join(self.ri.visualstudio, rf'{self.vs_ver:0.1f}\Setup\F#')
- return self.ri.lookup(path, 'productdir') or ''
- @property
- def UniversalCRTSdkDir(self) -> str | None:
- """
- Microsoft Universal CRT SDK directory.
- Return
- ------
- str | None
- path
- """
- # Set Kit Roots versions for specified MSVC++ version
- vers = ('10', '81') if self.vs_ver >= 14.0 else ()
- # Find path of the more recent Kit
- for ver in vers:
- sdkdir = self.ri.lookup(self.ri.windows_kits_roots, f'kitsroot{ver}')
- if sdkdir:
- return sdkdir
- return None
- @property
- def UniversalCRTSdkLastVersion(self) -> str:
- """
- Microsoft Universal C Runtime SDK last version.
- Return
- ------
- str
- version
- """
- try:
- return self._use_last_dir_name(os.path.join(self.UniversalCRTSdkDir, 'lib')) # type: ignore[arg-type] # Expected TypeError
- except TypeError as ex:
- py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
- raise
- @property
- def NetFxSdkVersion(self) -> tuple[LiteralString, ...]:
- """
- Microsoft .NET Framework SDK versions.
- Return
- ------
- tuple of str
- versions
- """
- # Set FxSdk versions for specified VS version
- return (
- ('4.7.2', '4.7.1', '4.7', '4.6.2', '4.6.1', '4.6', '4.5.2', '4.5.1', '4.5')
- if self.vs_ver >= 14.0
- else ()
- )
- @property
- def NetFxSdkDir(self) -> str | None:
- """
- Microsoft .NET Framework SDK directory.
- Return
- ------
- str | None
- path
- """
- sdkdir: str | None = ''
- for ver in self.NetFxSdkVersion:
- loc = os.path.join(self.ri.netfx_sdk, ver)
- sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
- if sdkdir:
- break
- return sdkdir
- @property
- def FrameworkDir32(self) -> str:
- """
- Microsoft .NET Framework 32bit directory.
- Return
- ------
- str
- path
- """
- # Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
- # Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
- @property
- def FrameworkDir64(self) -> str:
- """
- Microsoft .NET Framework 64bit directory.
- Return
- ------
- str
- path
- """
- # Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
- # Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
- @property
- def FrameworkVersion32(self) -> tuple[str, ...]:
- """
- Microsoft .NET Framework 32bit versions.
- Return
- ------
- tuple of str
- versions
- """
- return self._find_dot_net_versions(32)
- @property
- def FrameworkVersion64(self) -> tuple[str, ...]:
- """
- Microsoft .NET Framework 64bit versions.
- Return
- ------
- tuple of str
- versions
- """
- return self._find_dot_net_versions(64)
- def _find_dot_net_versions(self, bits) -> tuple[str, ...]:
- """
- Find Microsoft .NET Framework versions.
- Parameters
- ----------
- bits: int
- Platform number of bits: 32 or 64.
- Return
- ------
- tuple of str
- versions
- """
- # Find actual .NET version in registry
- reg_ver = self.ri.lookup(self.ri.vc, f'frameworkver{bits}')
- dot_net_dir = getattr(self, f'FrameworkDir{bits}')
- ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
- # Set .NET versions for specified MSVC++ version
- if self.vs_ver >= 12.0:
- return ver, 'v4.0'
- elif self.vs_ver >= 10.0:
- return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
- elif self.vs_ver == 9.0:
- return 'v3.5', 'v2.0.50727'
- elif self.vs_ver == 8.0:
- return 'v3.0', 'v2.0.50727'
- return ()
- @staticmethod
- def _use_last_dir_name(path: StrPath, prefix: str = '') -> str:
- """
- Return name of the last dir in path or '' if no dir found.
- Parameters
- ----------
- path: StrPath
- Use dirs in this path
- prefix: str
- Use only dirs starting by this prefix
- Return
- ------
- str
- name
- """
- matching_dirs = (
- dir_name
- for dir_name in reversed(os.listdir(path))
- if os.path.isdir(os.path.join(path, dir_name))
- and dir_name.startswith(prefix)
- )
- return next(matching_dirs, '')
- class _EnvironmentDict(TypedDict):
- include: str
- lib: str
- libpath: str
- path: str
- py_vcruntime_redist: NotRequired[str | None]
- class EnvironmentInfo:
- """
- Return environment variables for specified Microsoft Visual C++ version
- and platform : Lib, Include, Path and libpath.
- This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
- Script created by analysing Microsoft environment configuration files like
- "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
- Parameters
- ----------
- arch: str
- Target architecture.
- vc_ver: float
- Required Microsoft Visual C++ version. If not set, autodetect the last
- version.
- vc_min_ver: float
- Minimum Microsoft Visual C++ version.
- """
- # Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparison.
- def __init__(self, arch, vc_ver=None, vc_min_ver=0) -> None:
- self.pi = PlatformInfo(arch)
- self.ri = RegistryInfo(self.pi)
- self.si = SystemInfo(self.ri, vc_ver)
- if self.vc_ver < vc_min_ver:
- err = 'No suitable Microsoft Visual C++ version found'
- raise distutils.errors.DistutilsPlatformError(err)
- @property
- def vs_ver(self):
- """
- Microsoft Visual Studio.
- Return
- ------
- float
- version
- """
- return self.si.vs_ver
- @property
- def vc_ver(self):
- """
- Microsoft Visual C++ version.
- Return
- ------
- float
- version
- """
- return self.si.vc_ver
- @property
- def VSTools(self):
- """
- Microsoft Visual Studio Tools.
- Return
- ------
- list of str
- paths
- """
- paths = [r'Common7\IDE', r'Common7\Tools']
- if self.vs_ver >= 14.0:
- arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
- paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
- paths += [r'Team Tools\Performance Tools']
- paths += [rf'Team Tools\Performance Tools{arch_subdir}']
- return [os.path.join(self.si.VSInstallDir, path) for path in paths]
- @property
- def VCIncludes(self):
- """
- Microsoft Visual C++ & Microsoft Foundation Class Includes.
- Return
- ------
- list of str
- paths
- """
- return [
- os.path.join(self.si.VCInstallDir, 'Include'),
- os.path.join(self.si.VCInstallDir, r'ATLMFC\Include'),
- ]
- @property
- def VCLibraries(self):
- """
- Microsoft Visual C++ & Microsoft Foundation Class Libraries.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver >= 15.0:
- arch_subdir = self.pi.target_dir(x64=True)
- else:
- arch_subdir = self.pi.target_dir(hidex86=True)
- paths = [f'Lib{arch_subdir}', rf'ATLMFC\Lib{arch_subdir}']
- if self.vs_ver >= 14.0:
- paths += [rf'Lib\store{arch_subdir}']
- return [os.path.join(self.si.VCInstallDir, path) for path in paths]
- @property
- def VCStoreRefs(self):
- """
- Microsoft Visual C++ store references Libraries.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 14.0:
- return []
- return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
- @property
- def VCTools(self):
- """
- Microsoft Visual C++ Tools.
- Return
- ------
- list of str
- paths
- When host CPU is ARM, the tools should be found for ARM.
- >>> getfixture('windows_only')
- >>> mp = getfixture('monkeypatch')
- >>> mp.setattr(PlatformInfo, 'current_cpu', 'arm64')
- >>> ei = EnvironmentInfo(arch='irrelevant')
- >>> paths = ei.VCTools
- >>> any('HostARM64' in path for path in paths)
- True
- """
- si = self.si
- tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
- forcex86 = True if self.vs_ver <= 10.0 else False
- arch_subdir = self.pi.cross_dir(forcex86)
- if arch_subdir:
- tools += [os.path.join(si.VCInstallDir, f'Bin{arch_subdir}')]
- if self.vs_ver == 14.0:
- path = f'Bin{self.pi.current_dir(hidex86=True)}'
- tools += [os.path.join(si.VCInstallDir, path)]
- elif self.vs_ver >= 15.0:
- host_id = self.pi.current_cpu.replace('amd64', 'x64').upper()
- host_dir = os.path.join('bin', f'Host{host_id}%s')
- tools += [
- os.path.join(si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))
- ]
- if self.pi.current_cpu != self.pi.target_cpu:
- tools += [
- os.path.join(
- si.VCInstallDir, host_dir % self.pi.current_dir(x64=True)
- )
- ]
- else:
- tools += [os.path.join(si.VCInstallDir, 'Bin')]
- return tools
- @property
- def OSLibraries(self):
- """
- Microsoft Windows SDK Libraries.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver <= 10.0:
- arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
- return [os.path.join(self.si.WindowsSdkDir, f'Lib{arch_subdir}')]
- else:
- arch_subdir = self.pi.target_dir(x64=True)
- lib = os.path.join(self.si.WindowsSdkDir, 'lib')
- libver = self._sdk_subdir
- return [os.path.join(lib, f'{libver}um{arch_subdir}')]
- @property
- def OSIncludes(self):
- """
- Microsoft Windows SDK Include.
- Return
- ------
- list of str
- paths
- """
- include = os.path.join(self.si.WindowsSdkDir, 'include')
- if self.vs_ver <= 10.0:
- return [include, os.path.join(include, 'gl')]
- else:
- if self.vs_ver >= 14.0:
- sdkver = self._sdk_subdir
- else:
- sdkver = ''
- return [
- os.path.join(include, f'{sdkver}shared'),
- os.path.join(include, f'{sdkver}um'),
- os.path.join(include, f'{sdkver}winrt'),
- ]
- @property
- def OSLibpath(self):
- """
- Microsoft Windows SDK Libraries Paths.
- Return
- ------
- list of str
- paths
- """
- ref = os.path.join(self.si.WindowsSdkDir, 'References')
- libpath = []
- if self.vs_ver <= 9.0:
- libpath += self.OSLibraries
- if self.vs_ver >= 11.0:
- libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
- if self.vs_ver >= 14.0:
- libpath += [
- ref,
- os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
- os.path.join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
- os.path.join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
- os.path.join(
- ref, 'Windows.Networking.Connectivity.WwanContract', '1.0.0.0'
- ),
- os.path.join(
- self.si.WindowsSdkDir,
- 'ExtensionSDKs',
- 'Microsoft.VCLibs',
- f'{self.vs_ver:0.1f}',
- 'References',
- 'CommonConfiguration',
- 'neutral',
- ),
- ]
- return libpath
- @property
- def SdkTools(self):
- """
- Microsoft Windows SDK Tools.
- Return
- ------
- list of str
- paths
- """
- return list(self._sdk_tools())
- def _sdk_tools(self):
- """
- Microsoft Windows SDK Tools paths generator.
- Return
- ------
- generator of str
- paths
- """
- if self.vs_ver < 15.0:
- bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
- yield os.path.join(self.si.WindowsSdkDir, bin_dir)
- if not self.pi.current_is_x86():
- arch_subdir = self.pi.current_dir(x64=True)
- path = f'Bin{arch_subdir}'
- yield os.path.join(self.si.WindowsSdkDir, path)
- if self.vs_ver in (10.0, 11.0):
- if self.pi.target_is_x86():
- arch_subdir = ''
- else:
- arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
- path = rf'Bin\NETFX 4.0 Tools{arch_subdir}'
- yield os.path.join(self.si.WindowsSdkDir, path)
- elif self.vs_ver >= 15.0:
- path = os.path.join(self.si.WindowsSdkDir, 'Bin')
- arch_subdir = self.pi.current_dir(x64=True)
- sdkver = self.si.WindowsSdkLastVersion
- yield os.path.join(path, f'{sdkver}{arch_subdir}')
- if self.si.WindowsSDKExecutablePath:
- yield self.si.WindowsSDKExecutablePath
- @property
- def _sdk_subdir(self) -> str:
- """
- Microsoft Windows SDK version subdir.
- Return
- ------
- str
- subdir
- """
- ucrtver = self.si.WindowsSdkLastVersion
- return (f'{ucrtver}\\') if ucrtver else ''
- @property
- def SdkSetup(self):
- """
- Microsoft Windows SDK Setup.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver > 9.0:
- return []
- return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
- @property
- def FxTools(self):
- """
- Microsoft .NET Framework Tools.
- Return
- ------
- list of str
- paths
- """
- pi = self.pi
- si = self.si
- if self.vs_ver <= 10.0:
- include32 = True
- include64 = not pi.target_is_x86() and not pi.current_is_x86()
- else:
- include32 = pi.target_is_x86() or pi.current_is_x86()
- include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64'
- tools = []
- if include32:
- tools += [
- os.path.join(si.FrameworkDir32, ver) for ver in si.FrameworkVersion32
- ]
- if include64:
- tools += [
- os.path.join(si.FrameworkDir64, ver) for ver in si.FrameworkVersion64
- ]
- return tools
- @property
- def NetFxSDKLibraries(self):
- """
- Microsoft .Net Framework SDK Libraries.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
- return []
- arch_subdir = self.pi.target_dir(x64=True)
- return [os.path.join(self.si.NetFxSdkDir, rf'lib\um{arch_subdir}')]
- @property
- def NetFxSDKIncludes(self):
- """
- Microsoft .Net Framework SDK Includes.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
- return []
- return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
- @property
- def VsTDb(self):
- """
- Microsoft Visual Studio Team System Database.
- Return
- ------
- list of str
- paths
- """
- return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
- @property
- def MSBuild(self):
- """
- Microsoft Build Engine.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 12.0:
- return []
- elif self.vs_ver < 15.0:
- base_path = self.si.ProgramFilesx86
- arch_subdir = self.pi.current_dir(hidex86=True)
- else:
- base_path = self.si.VSInstallDir
- arch_subdir = ''
- path = rf'MSBuild\{self.vs_ver:0.1f}\bin{arch_subdir}'
- build = [os.path.join(base_path, path)]
- if self.vs_ver >= 15.0:
- # Add Roslyn C# & Visual Basic Compiler
- build += [os.path.join(base_path, path, 'Roslyn')]
- return build
- @property
- def HTMLHelpWorkshop(self):
- """
- Microsoft HTML Help Workshop.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 11.0:
- return []
- return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
- @property
- def UCRTLibraries(self) -> list[str]:
- """
- Microsoft Universal C Runtime SDK Libraries.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 14.0:
- return []
- arch_subdir = self.pi.target_dir(x64=True)
- try:
- lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') # type: ignore[arg-type] # Expected TypeError
- except TypeError as ex:
- py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
- raise
- ucrtver = self._ucrt_subdir
- return [os.path.join(lib, f'{ucrtver}ucrt{arch_subdir}')]
- @property
- def UCRTIncludes(self) -> list[str]:
- """
- Microsoft Universal C Runtime SDK Include.
- Return
- ------
- list of str
- paths
- """
- if self.vs_ver < 14.0:
- return []
- try:
- include = os.path.join(self.si.UniversalCRTSdkDir, 'include') # type: ignore[arg-type] # Expected TypeError
- except TypeError as ex:
- py310.add_note(ex, "Cannot find UniversalCRTSdkDir")
- raise
- return [os.path.join(include, f'{self._ucrt_subdir}ucrt')]
- @property
- def _ucrt_subdir(self) -> str:
- """
- Microsoft Universal C Runtime SDK version subdir.
- Return
- ------
- str
- subdir
- """
- ucrtver = self.si.UniversalCRTSdkLastVersion
- return (f'{ucrtver}\\') if ucrtver else ''
- @property
- def FSharp(self):
- """
- Microsoft Visual F#.
- Return
- ------
- list of str
- paths
- """
- if 11.0 > self.vs_ver > 12.0:
- return []
- return [self.si.FSharpInstallDir]
- @property
- def VCRuntimeRedist(self) -> str | None:
- """
- Microsoft Visual C++ runtime redistributable dll.
- Returns the first suitable path found or None.
- """
- vcruntime = f'vcruntime{self.vc_ver}0.dll'
- arch_subdir = self.pi.target_dir(x64=True).strip('\\')
- # Installation prefixes candidates
- prefixes = []
- tools_path = self.si.VCInstallDir
- redist_path = os.path.dirname(tools_path.replace(r'\Tools', r'\Redist'))
- if os.path.isdir(redist_path):
- # Redist version may not be exactly the same as tools
- redist_path = os.path.join(redist_path, os.listdir(redist_path)[-1])
- prefixes += [redist_path, os.path.join(redist_path, 'onecore')]
- prefixes += [os.path.join(tools_path, 'redist')] # VS14 legacy path
- # CRT directory
- crt_dirs = (
- f'Microsoft.VC{self.vc_ver * 10}.CRT',
- # Sometime store in directory with VS version instead of VC
- f'Microsoft.VC{int(self.vs_ver) * 10}.CRT',
- )
- # vcruntime path
- candidate_paths = (
- os.path.join(prefix, arch_subdir, crt_dir, vcruntime)
- for (prefix, crt_dir) in itertools.product(prefixes, crt_dirs)
- )
- return next(filter(os.path.isfile, candidate_paths), None) # type: ignore[arg-type] #python/mypy#12682
- def return_env(self, exists: bool = True) -> _EnvironmentDict:
- """
- Return environment dict.
- Parameters
- ----------
- exists: bool
- It True, only return existing paths.
- Return
- ------
- dict
- environment
- """
- env = _EnvironmentDict(
- include=self._build_paths(
- 'include',
- [
- self.VCIncludes,
- self.OSIncludes,
- self.UCRTIncludes,
- self.NetFxSDKIncludes,
- ],
- exists,
- ),
- lib=self._build_paths(
- 'lib',
- [
- self.VCLibraries,
- self.OSLibraries,
- self.FxTools,
- self.UCRTLibraries,
- self.NetFxSDKLibraries,
- ],
- exists,
- ),
- libpath=self._build_paths(
- 'libpath',
- [self.VCLibraries, self.FxTools, self.VCStoreRefs, self.OSLibpath],
- exists,
- ),
- path=self._build_paths(
- 'path',
- [
- self.VCTools,
- self.VSTools,
- self.VsTDb,
- self.SdkTools,
- self.SdkSetup,
- self.FxTools,
- self.MSBuild,
- self.HTMLHelpWorkshop,
- self.FSharp,
- ],
- exists,
- ),
- )
- if self.vs_ver >= 14 and self.VCRuntimeRedist:
- env['py_vcruntime_redist'] = self.VCRuntimeRedist
- return env
- def _build_paths(self, name, spec_path_lists, exists):
- """
- Given an environment variable name and specified paths,
- return a pathsep-separated string of paths containing
- unique, extant, directories from those paths and from
- the environment variable. Raise an error if no paths
- are resolved.
- Parameters
- ----------
- name: str
- Environment variable name
- spec_path_lists: list of str
- Paths
- exists: bool
- It True, only return existing paths.
- Return
- ------
- str
- Pathsep-separated paths
- """
- # flatten spec_path_lists
- spec_paths = itertools.chain.from_iterable(spec_path_lists)
- env_paths = environ.get(name, '').split(os.pathsep)
- paths = itertools.chain(spec_paths, env_paths)
- extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
- if not extant_paths:
- msg = f"{name.upper()} environment variable is empty"
- raise distutils.errors.DistutilsPlatformError(msg)
- unique_paths = unique_everseen(extant_paths)
- return os.pathsep.join(unique_paths)
|