├── LICENSE ├── .gitignore ├── pyproject.toml ├── .github └── workflows │ └── python-publish.yml ├── README.md └── pyMSVC ├── vswhere.py └── __init__.py /LICENSE: -------------------------------------------------------------------------------- 1 | *********************************************************************************** 2 | MIT License 3 | 4 | Copyright (c) 2020 Kevin G. Schlosser 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is furnished 11 | to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 17 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 20 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | *********************************************************************************** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pyMSVC" 3 | version = "0.6.1" 4 | description = "Simple to use MSVC build environment setup tool." 5 | requires-python = ">=3.6" 6 | dependencies = ["comtypes==1.4.13"] 7 | authors = [{name = "Kevin Schlosser"}] 8 | license = {file = "LICENSE"} 9 | readme = {file = "README.md", content-type = "text/markdown"} 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Environment :: Win32 (MS Windows)", 13 | "Framework :: Setuptools Plugin", 14 | "Intended Audience :: Developers", 15 | "License :: OSI Approved :: MIT License", 16 | "Natural Language :: English", 17 | "Programming Language :: Python :: 3 :: Only", 18 | "Programming Language :: Python :: 3.8", 19 | "Programming Language :: Python :: 3.9", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Programming Language :: Python :: 3.12", 23 | "Programming Language :: Python :: 3.13", 24 | "Programming Language :: Cython", 25 | "Programming Language :: C", 26 | "Programming Language :: C++", 27 | "Programming Language :: C#", 28 | "Programming Language :: F#", 29 | "Programming Language :: Python :: Free Threading", 30 | "Operating System :: Microsoft :: Windows", 31 | "Operating System :: Microsoft :: Windows :: Windows 7", 32 | "Operating System :: Microsoft :: Windows :: Windows 8", 33 | "Operating System :: Microsoft :: Windows :: Windows 8.1", 34 | "Operating System :: Microsoft :: Windows :: Windows 10", 35 | "Operating System :: Microsoft :: Windows :: Windows 11", 36 | "Topic :: Software Development :: Build Tools", 37 | "Topic :: Software Development :: Compilers" 38 | ] 39 | 40 | [project.urls] 41 | Homepage = "https://github.com/kdschlosser/python_msvc" 42 | Issues = "https://github.com/kdschlosser/python_msvc/issues" 43 | 44 | [build-system] 45 | requires = ["setuptools>=61.0"] 46 | build-backend = "setuptools.build_meta" 47 | 48 | [tool.setuptools.packages.find] 49 | where = ["."] 50 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package to PyPI when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | release-build: 20 | runs-on: windows-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.x" 28 | 29 | - name: Build release distributions 30 | run: | 31 | # NOTE: put your own distribution build steps here. 32 | python -m pip install build 33 | python -m build 34 | 35 | - name: Upload distributions 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: release-dists 39 | path: dist/ 40 | 41 | pypi-publish: 42 | runs-on: ubuntu-latest 43 | needs: 44 | - release-build 45 | permissions: 46 | # IMPORTANT: this permission is mandatory for trusted publishing 47 | id-token: write 48 | 49 | # Dedicated environments with protections for publishing are strongly recommended. 50 | # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules 51 | environment: 52 | name: pypi 53 | # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status: 54 | # url: https://pypi.org/p/YOURPROJECT 55 | # 56 | # ALTERNATIVE: if your GitHub Release name is the PyPI project version string 57 | # ALTERNATIVE: exactly, uncomment the following line instead: 58 | # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }} 59 | 60 | steps: 61 | - name: Retrieve release distributions 62 | uses: actions/download-artifact@v4 63 | with: 64 | name: release-dists 65 | path: dist/ 66 | 67 | - name: Publish release distributions to PyPI 68 | uses: pypa/gh-action-pypi-publish@release/v1 69 | with: 70 | packages-dir: dist/ 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyMSVC 2 | 3 | A fairly stupid proof build environment setup for compiling c extensions using pythons distutils or setuptools. 4 | 5 | I created this module because distutils does not do a good job at setting up a Windows build environment. 6 | Distutils relies on static paths for the MSVC compiler It does not detect Visual Studio Build tools installations. And 7 | the largest problem with it is that it uses the vcvars*.bat files included with the compiler. It runs a subprocess 8 | takes a snapshot of the computer's environment then runs vcvarsall and takes another snapshot of the environment. It 9 | then compares the before and after and stores the differences between the 2. Now this would be a fantastic approach if 10 | Microsoft had made the vcvars*.bat files in a manner that would et up a correct build environment. There have been known issues 11 | using these files in the past. 12 | 13 | The single largest problem with the vcvars files is NONE of them check to see if any of the components actually exist. 14 | We all know how good applications are when they get uninstalled. They do a very thorough job of removing all traces 15 | from the registry. NOT. Microsoft is the biggest offender of this. 16 | 17 | My system does not use the vcvars* files the information is obtained from the registry. The paths that are obtained are 18 | checked to ensure they exist. 19 | 20 | Setuptools is an improvement over distutils but still relies on those vcvars* files. 21 | 22 | `pyMSVC.Environment(strict_visual_c_version=None, minimum_visual_c_version=None)` 23 | 24 | If both strict_visual_c_version and minimum_visual_c_version are None that the program runs as a free for all and 25 | whichever installation it finds first is the winner. 26 | 27 | Both parameters accept either None or a float that is the version number fo the MSVC compiler that you want to use. 28 | 29 | * 10.0 Visual Studio 2010 30 | * 11.0 Visual Studio 2012 31 | * 12.0 Visual Studio 2013 32 | * 14.0 Visual Studio 2015 33 | * 14.1x Visual Studio 2017 34 | * 14.2x Visual Studio 2019 35 | * 14.3x Visual Studio 2022 36 | 37 | Python recommends that any extensions that are compiled on Windows should be compiled with the same MSVC version 38 | that was used to compile Python. This is due to incompatibilities in the common runtime language between the 39 | compiler versions. You may or may not experience any issues if you do not use the same compiler version. Your extension 40 | would have to use that portion of the CLR in order to have an issue. This is why it is a recommendation. 41 | 42 | 43 | Python versions 3.9 and 3.10 use MSVC Version 14.2 44 | Python versions 3.5, 3.6, 3.7, 3.8 use any of the MSVC compiler version 14.x 45 | Python version 3.4 uses MSVC compiler version 10.0 46 | 47 | If you would like to have the above done for you automatically and have the environment set up. You can use 48 | `environment = pyMSVC.setup_environment()`. This will rise an exception if the msvc version that is needed based on the 49 | python version is not found. This will set up the environment for you as well without the need for any additional steps. 50 | 51 | I added the minimum_visual_c_version parameter which allows you to specify a minimum compiler version to use. 52 | You may have code that will compile using MSVC 10.0 but will fail if compiled using MSVC 9.0. 53 | 54 | So to sum it up. 55 | strict_visual_c_version you will use if only a specific compiler version is to be used to compile. 56 | minimum_visual_c_version you will use if compiler versions that are the same or higher as the one specified are ok to use 57 | 58 | Here is an example of using pyMSVC to only set up the build environment. 59 | 60 | import os 61 | import pyMSVC 62 | 63 | environment = pyMSVC.Environment() 64 | print(environment) 65 | 66 | os.environ.update(environment) 67 | 68 | 69 | You will want to set up the environment before you import distutils or setuptools. 70 | 71 | Now onto the goodies. This module provides access to a bucket load of information. 72 | 73 | 74 | Here are the properties and attributes available 75 | 76 | 77 | ###### class ***Environment*** 78 | * ***machine_architecture***: Windows architecture x86 or x64 79 | * ***platform***: x86 or x64, if running Windows x86 this will be x86. if running Windows x64 and Python x86 this will return x86. 80 | * ***build_environment***: returns a `dict` of the environment 81 | * ***visual_c***: instance of VisualCInfo 82 | * ***visual_studio***: instance of VisualStudioInfo 83 | * ***windows_sdk***: instance of WindowsSDKInfo 84 | * ***dot_net***: instance of NETInfo 85 | * ***python***: instance of PythonInfo 86 | 87 | 88 | ###### class ***NETInfo*** 89 | * ***version***: .NET version based on the platform architecture 90 | * ***version_32***: 32bit .NET version 91 | * ***version_64***: 64bit .NET version 92 | * ***directory***: directory to .NET based on preffered bitness 93 | * ***directory_32***: 32bit .NET directory 94 | * ***directory_64***: 64bit .NET directory 95 | * ***preferred_bitness***: .NET bitness 96 | * ***netfx_sdk_directory***: .NET FX SDK path 97 | * ***net_fx_tools_directory***: .NET FX tools path using preffered bitness 98 | * ***net_tools***: .NET tools paths 99 | * ***executable_path_x64***: 64bit executable path 100 | * ***executable_path_x86***: 32 bit execitable path 101 | * ***lib***: library paths 102 | 103 | 104 | ###### class ***WindowsSDKInfo*** 105 | * ***extension_sdk_directory***: Extension SDK path 106 | * ***ucrt_version***: UCRT version 107 | * ***ucrt_sdk_directory***: Path to the UCRT libraries 108 | * ***bin_path***: BIN path 109 | * ***lib***: paths to the libraries. 110 | * ***type_script_path***: path to TypeScript 111 | * ***include***: include paths 112 | * ***sdk_lib_path***: 113 | * ***windows_sdks***: Acceptable SDK versions based on compiler version 114 | * ***version***: SDK version 115 | * ***sdk_version***: Actual SDK version 116 | * ***directory***: path to the Windows SDK 117 | 118 | 119 | ###### class ***VisualStudioInfo*** 120 | * ***install_directory***: installation directory 121 | * ***dev_env_directory***: directory where devenv.exe is located 122 | * ***common_tools***: path to tools 123 | * ***common_version***: example - 2019 124 | * ***uncommon_version***: example - 16.4.5 125 | * ***version***: VS major version 126 | 127 | 128 | ###### class ***VisualCInfo*** 129 | * ***f_sharp_path***: FSharp path 130 | * ***ide_install_directory***: path to Visual C ide 131 | * ***install_directory***: path to Visual C 132 | * ***version***: Visual C version 133 | * ***tools_version***: Visual C tool version 134 | * ***toolset_version***: Visual C Toolset Version - v141, v140, v120 etc... 135 | * ***msvc_dll_version***: MSVC dll version 136 | * ***msvc_dll_path***: Location of the MSVC dll 137 | * ***tools_redist_directory***: Visual C redist path 138 | * ***tools_install_directory***: Visual C tools installation folder 139 | * ***msbuild_version***: MSBuild version 140 | * ***msbuild_path***: MSBuild path 141 | * ***html_help_path***: HTML Help path 142 | * ***atlmfc_lib_path***: ATLMFC library path 143 | * ***lib***: Visual C library path 144 | * ***atlmfc_path***: ATLMFC path 145 | * ***atlmfc_include_path***: ATLMFC include path 146 | * ***include***: Visual C include folders 147 | 148 | 149 | ###### class ***PythonInfo*** 150 | * ***architecture***: x86 or x64 151 | * ***version***: Python version 152 | * ***dependency***: library name.. Python27.lib 153 | * ***includes***: include paths 154 | * ***libraries***: library paths 155 | 156 | 157 | Starting with Visual Studio 2017 Microsoft has added a COM interface that I am able to use 158 | to collect information about the installed versions of Visual Studio. 159 | 160 | In order to collect pyMSVC to be able to use it in your build system you will 161 | need to create a pyproject.toml file. In that file you need to have the following lines. 162 | 163 | [build-system] 164 | requires=["setuptools", "wheel", "pyMSVC;sys_platform=='win32'"] 165 | build-backend="setuptools.build_meta" 166 | 167 | This will instruct pip to collect pyMSVC if the OS is Windows. 168 | In your setup.py file you want this at the top 169 | 170 | import sys 171 | 172 | if sys.platform.startswith('win'): 173 | import pyMSVC 174 | environment = pyMSVC.setup_environment() 175 | print(environment) 176 | 177 | import setuptools 178 | 179 | # rest of setup code here 180 | 181 | The code above does everything for you. you do not need to create the 182 | environment using `os.environ` and you do not have to pass any versions. 183 | It will locate the compiler needed automatically if it is instaleld. 184 | 185 | It's really that easy to use. You really don't have know the inner working of 186 | this library in order for it to be able to set up a proper build environment. 187 | 188 | 189 | ###### New additions 190 | 191 | I added in the ability to check and see if Cmake is available as well as Ninja. 192 | The reason I added this ability is because you can use Cmake to write the code 193 | needed for Ninja to run. So what so important about being able to do that you ask? 194 | You are able to compile each source file in parallel. On multicore machines that 195 | can really cut down on the compile time. Herte is an example. Sat it take 60 seconds 196 | for your project to compile. say you have 32 logical processors in your machine (like me) 197 | If you launch ninja with -j32 you will use 100% of the machines processing power to 198 | compile your project. so 60 / 32 = 1.875 seconds. It is not going to take exactly 1.875 199 | seconds but it gives you a decent idea. One of the projects I have takes about 1 minute 200 | to compile normally and with Ninja it takes about 7 seconds. 201 | 202 | Example of use 203 | 204 | import subprocess 205 | import sys 206 | import os 207 | 208 | if sys.platform.startswith('win'): 209 | import pyMSVC 210 | environment = pyMSVC.setup_environment() 211 | print(environment) 212 | 213 | if environment.visual_c.has_cmake and environment.visual_c.has_ninja: 214 | subprocess.run('cmake -G Ninja') 215 | subprocess.run(f'ninja -j{os.cpu_count()}') 216 | 217 | import setuptools 218 | 219 | 220 | you will not need to use setuptools or distutils to compile. you have to add the compiled files to your library once cmake as finished the compilations. 221 | -------------------------------------------------------------------------------- /pyMSVC/vswhere.py: -------------------------------------------------------------------------------- 1 | # ############################################################################# 2 | # 3 | # MIT License 4 | # 5 | # Copyright 2022 Kevin G. Schlosser 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | # ############################################################################# 26 | 27 | try: 28 | import comtypes 29 | except ImportError: 30 | raise RuntimeError('the comtypes library is needed to run this script') 31 | 32 | import weakref 33 | import ctypes 34 | import datetime 35 | from ctypes.wintypes import ( 36 | WORD, 37 | DWORD, 38 | BOOL, 39 | LPCOLESTR, 40 | ULONG, 41 | LPCWSTR, 42 | LPVOID, 43 | LCID 44 | ) 45 | 46 | from comtypes.automation import ( 47 | tagVARIANT, 48 | BSTR 49 | ) 50 | from comtypes import ( 51 | GUID, 52 | COMMETHOD, 53 | POINTER, 54 | IUnknown, 55 | HRESULT 56 | ) 57 | from comtypes._safearray import ( # NOQA 58 | SAFEARRAY, 59 | SafeArrayLock as _SafeArrayLock, 60 | SafeArrayUnlock as _SafeArrayUnlock 61 | ) 62 | 63 | VARIANT_BOOL = ctypes.c_short 64 | LPVARIANT = POINTER(tagVARIANT) 65 | VARIANT = tagVARIANT 66 | ENUM = ctypes.c_uint 67 | IID = GUID 68 | CLSID = GUID 69 | MAXUINT = 0xFFFFFFFF 70 | PULONGLONG = POINTER(ctypes.c_ulonglong) 71 | LPSAFEARRAY = POINTER(SAFEARRAY) 72 | _CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree 73 | 74 | kernel32 = ctypes.windll.kernel32 75 | 76 | _GetUserDefaultLCID = kernel32.GetUserDefaultLCID 77 | _GetUserDefaultLCID.restype = LCID 78 | 79 | 80 | def HRESULT_FROM_WIN32(x): 81 | return x 82 | 83 | 84 | ERROR_FILE_NOT_FOUND = 0x00000002 85 | ERROR_NOT_FOUND = 0x00000490 86 | 87 | # Constants 88 | E_NOTFOUND = HRESULT_FROM_WIN32(ERROR_NOT_FOUND) 89 | E_FILENOTFOUND = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) 90 | 91 | _kernel32 = ctypes.windll.kernel32 92 | 93 | _FileTimeToSystemTime = _kernel32.FileTimeToSystemTime 94 | _FileTimeToSystemTime.restype = BOOL 95 | 96 | _SystemTimeToFileTime = _kernel32.SystemTimeToFileTime 97 | _SystemTimeToFileTime.restype = BOOL 98 | 99 | 100 | class _FILETIME(ctypes.Structure): 101 | _fields_ = [ 102 | ('dwLowDateTime', DWORD), 103 | ('dwHighDateTime', DWORD) 104 | ] 105 | 106 | @property 107 | def value(self): 108 | system_time = SYSTEMTIME() 109 | _FileTimeToSystemTime(ctypes.byref(self), ctypes.byref(system_time)) 110 | return system_time.value 111 | 112 | @value.setter 113 | def value(self, dt): 114 | system_time = SYSTEMTIME() 115 | system_time.value = dt 116 | _SystemTimeToFileTime(ctypes.byref(system_time), ctypes.byref(self)) 117 | 118 | 119 | FILETIME = _FILETIME 120 | LPFILETIME = POINTER(FILETIME) 121 | 122 | 123 | class _SYSTEMTIME(ctypes.Structure): 124 | _fields_ = [ 125 | ('wYear', WORD), 126 | ('wMonth', WORD), 127 | ('wDayOfWeek', WORD), 128 | ('wDay', WORD), 129 | ('wHour', WORD), 130 | ('wMinute', WORD), 131 | ('wSecond', WORD), 132 | ('wMilliseconds', WORD), 133 | ] 134 | 135 | @property 136 | def value(self): 137 | dt = datetime.datetime( 138 | year=self.wYear, 139 | month=self.wMonth, 140 | day=self.wDay, 141 | hour=self.wHour, 142 | minute=self.wMinute, 143 | second=self.wSecond, 144 | microsecond=self.wMilliseconds * 1000 145 | ) 146 | 147 | return dt 148 | 149 | # noinspection PyAttributeOutsideInit 150 | @value.setter 151 | def value(self, dt): 152 | if isinstance(dt, (int, float)): 153 | dt = datetime.datetime.fromtimestamp(dt) 154 | 155 | weekday = dt.weekday() + 1 156 | if weekday == 7: 157 | weekday = 0 158 | 159 | self.wYear = dt.year 160 | self.wMonth = dt.month 161 | self.wDayOfWeek = weekday 162 | self.wDay = dt.day 163 | self.wHour = dt.hour 164 | self.wMinute = dt.minute 165 | self.wSecond = dt.second 166 | self.wMilliseconds = int(dt.microsecond / 1000) 167 | 168 | 169 | SYSTEMTIME = _SYSTEMTIME 170 | 171 | 172 | # Enumerations 173 | # The state of an instance. 174 | class InstanceState(ENUM): 175 | # The instance state has not been determined. 176 | eNone = 0 177 | # The instance installation path exists. 178 | eLocal = 1 179 | # A product is registered to the instance. 180 | eRegistered = 2 181 | # No reboot is required for the instance. 182 | eNoRebootRequired = 4 183 | # do not know what this bit does 184 | eNoErrors = 8 185 | # The instance represents a complete install. 186 | eComplete = MAXUINT 187 | 188 | @property 189 | def value(self): 190 | # noinspection PyUnresolvedReferences 191 | value = ENUM.value.__get__(self) 192 | if value == self.eComplete: 193 | return ['local', 'registered', 'no reboot required'] 194 | if value == self.eNone: 195 | return ['remote', 'unregistered', 'reboot required'] 196 | 197 | res = [] 198 | 199 | if value | self.eLocal == value: 200 | res += ['local'] 201 | else: 202 | res += ['remote'] 203 | if value | self.eRegistered == value: 204 | res += ['registered'] 205 | else: 206 | res += ['unregistered'] 207 | if value | self.eNoRebootRequired == value: 208 | res += ['no reboot required'] 209 | else: 210 | res += ['reboot required'] 211 | if value | self.eNoErrors == value: 212 | res.append('no errors') 213 | else: 214 | res.append('errors') 215 | 216 | return res 217 | 218 | 219 | eNone = InstanceState.eNone 220 | eLocal = InstanceState.eLocal 221 | eRegistered = InstanceState.eRegistered 222 | eNoRebootRequired = InstanceState.eNoRebootRequired 223 | eNoErrors = InstanceState.eNoErrors 224 | eComplete = InstanceState.eComplete 225 | 226 | 227 | class Packages(object): 228 | 229 | def __init__(self, packages): 230 | self._packages = packages 231 | 232 | def __iter__(self): 233 | return iter(self._packages) 234 | 235 | def __str__(self): 236 | res = [] 237 | 238 | def _add(items): 239 | for item in items: 240 | res.append( 241 | '\n'.join( 242 | ' ' + line 243 | for line in str(item).split('\n') 244 | ) 245 | ) 246 | res.append('') 247 | 248 | res.append('vsix:') 249 | _add(self.vsix) 250 | res.append('group:') 251 | _add(self.group) 252 | res.append('component:') 253 | _add(self.component) 254 | res.append('workload:') 255 | _add(self.workload) 256 | res.append('product:') 257 | _add(self.product) 258 | res.append('msi:') 259 | _add(self.msi) 260 | res.append('exe:') 261 | _add(self.exe) 262 | res.append('msu:') 263 | _add(self.msu) 264 | res.append('other:') 265 | _add(self.other) 266 | 267 | return '\n'.join(res) 268 | 269 | @property 270 | def vsix(self): 271 | return [ 272 | package for package in self 273 | if package.type == 'Vsix' 274 | ] 275 | 276 | @property 277 | def group(self): 278 | return [ 279 | package for package in self 280 | if package.type == 'Group' 281 | ] 282 | 283 | @property 284 | def component(self): 285 | return [ 286 | package for package in self 287 | if package.type == 'Component' 288 | ] 289 | 290 | @property 291 | def workload(self): 292 | return [ 293 | package for package in self 294 | if package.type == 'Workload' 295 | ] 296 | 297 | @property 298 | def product(self): 299 | return [ 300 | package for package in self 301 | if package.type == 'Product' 302 | ] 303 | 304 | @property 305 | def msi(self): 306 | return [ 307 | package for package in self 308 | if package.type == 'Msi' 309 | ] 310 | 311 | @property 312 | def exe(self): 313 | return [ 314 | package for package in self 315 | if package.type == 'Exe' 316 | ] 317 | 318 | @property 319 | def msu(self): 320 | return [ 321 | package for package in self 322 | if package.type == 'Msu' 323 | ] 324 | 325 | @property 326 | def other(self): 327 | return [ 328 | package for package in self 329 | if package.type not in ( 330 | 'Exe', 'Msi', 'Product', 'Vsix', 331 | 'Group', 'Component', 'Workload', 'Msu' 332 | ) 333 | ] 334 | 335 | 336 | # Forward declarations 337 | 338 | 339 | IID_ISetupPackageReference = IID("{da8d8a16-b2b6-4487-a2f1-594ccccd6bf5}") 340 | 341 | 342 | # A reference to a package. 343 | class ISetupPackageReference(IUnknown): 344 | _iid_ = IID_ISetupPackageReference 345 | 346 | def __gt__(self, other): 347 | if isinstance(other, ISetupPackageReference): 348 | return self.version > other.version 349 | 350 | other = _convert_version(other) 351 | 352 | if not isinstance(other, tuple): 353 | return False 354 | 355 | return self.version > other 356 | 357 | def __lt__(self, other): 358 | if isinstance(other, ISetupPackageReference): 359 | return self.version < other.version 360 | 361 | other = _convert_version(other) 362 | 363 | if not isinstance(other, str): 364 | return False 365 | 366 | return self.version < other 367 | 368 | def __ge__(self, other): 369 | if isinstance(other, ISetupPackageReference): 370 | return self.version >= other.version 371 | 372 | other = _convert_version(other) 373 | 374 | if not isinstance(other, str): 375 | return False 376 | 377 | return self.version >= other 378 | 379 | def __le__(self, other): 380 | if isinstance(other, ISetupPackageReference): 381 | return self.version <= other.version 382 | 383 | other = _convert_version(other) 384 | 385 | if not isinstance(other, str): 386 | return False 387 | 388 | return self.version <= other 389 | 390 | def __eq__(self, other): 391 | if isinstance(other, ISetupPackageReference): 392 | return self.version == other.version 393 | 394 | other = _convert_version(other) 395 | 396 | if not isinstance(other, str): 397 | return object.__eq__(self, other) 398 | 399 | return self.version == other 400 | 401 | def __ne__(self, other): 402 | return not self.__eq__(other) 403 | 404 | @property 405 | def name(self): 406 | # noinspection PyUnresolvedReferences 407 | return self.GetId() 408 | 409 | @property 410 | def id(self): 411 | # noinspection PyUnresolvedReferences 412 | return self.GetId() 413 | 414 | @property 415 | def version(self): 416 | # noinspection PyUnresolvedReferences 417 | return self.GetVersion() 418 | 419 | @property 420 | def chip(self): 421 | # noinspection PyUnresolvedReferences 422 | return self.GetChip() 423 | 424 | @property 425 | def language(self): 426 | # noinspection PyUnresolvedReferences 427 | return self.GetLanguage() 428 | 429 | @property 430 | def branch(self): 431 | # noinspection PyUnresolvedReferences 432 | return self.GetBranch() 433 | 434 | @property 435 | def type(self): 436 | # noinspection PyUnresolvedReferences 437 | return self.GetType() 438 | 439 | @property 440 | def unique_id(self): 441 | # noinspection PyUnresolvedReferences 442 | return self.GetUniqueId() 443 | 444 | @property 445 | def is_extension(self): 446 | # noinspection PyUnresolvedReferences 447 | return self.GetIsExtension() 448 | 449 | def __str__(self): 450 | res = [ 451 | 'id: ' + str(self.id), 452 | 'version: ' + str(self.version), 453 | 'chip: ' + str(self.chip), 454 | 'language: ' + str(self.language), 455 | 'branch: ' + str(self.branch), 456 | 'type: ' + str(self.type), 457 | 'unique id: ' + str(self.unique_id), 458 | 'is extension: ' + str(self.is_extension) 459 | ] 460 | return '\n'.join(res) 461 | 462 | 463 | IID_ISetupInstance = IID("{B41463C3-8866-43B5-BC33-2B0676F7F42E}") 464 | 465 | 466 | def _convert_version(other): 467 | if isinstance(other, str): 468 | other = tuple(int(item) for item in other.split('.')) 469 | elif isinstance(other, bytes): 470 | other = tuple( 471 | int(item) for item in other.decode('utf-8').split('.') 472 | ) 473 | elif isinstance(other, list): 474 | other = tuple(other) 475 | elif isinstance(other, int): 476 | other = (other,) 477 | elif isinstance(other, float): 478 | other = tuple(int(item) for item in str(other).split('.')) 479 | 480 | if isinstance(other, tuple): 481 | other = '.'.join(str(item) for item in other) 482 | 483 | return other 484 | 485 | 486 | # Information about an instance of a product. 487 | class ISetupInstance(IUnknown): 488 | _iid_ = IID_ISetupInstance 489 | _helper = None 490 | 491 | def __call__(self, helper): 492 | self._helper = helper 493 | return self 494 | 495 | @property 496 | def id(self): 497 | # noinspection PyUnresolvedReferences 498 | return self.GetInstanceId() 499 | 500 | @property 501 | def install_date(self): 502 | # noinspection PyUnresolvedReferences 503 | return self.GetInstallDate().value 504 | 505 | @property 506 | def name(self): 507 | # noinspection PyUnresolvedReferences 508 | return self.GetInstallationName() 509 | 510 | @property 511 | def path(self): 512 | # noinspection PyUnresolvedReferences 513 | return self.GetInstallationPath() 514 | 515 | @property 516 | def version(self): 517 | # noinspection PyUnresolvedReferences 518 | return self.GetInstallationVersion() 519 | 520 | @property 521 | def full_version(self): 522 | if self._helper is not None: 523 | return self._helper.ParseVersion(self.version) 524 | 525 | @property 526 | def display_name(self): 527 | try: 528 | # noinspection PyUnresolvedReferences 529 | return self.GetDisplayName(_GetUserDefaultLCID()) 530 | except (OSError, ValueError, comtypes.COMError): 531 | pass 532 | 533 | @property 534 | def description(self): 535 | try: 536 | # noinspection PyUnresolvedReferences 537 | return self.GetDescription(_GetUserDefaultLCID()) 538 | except (OSError, ValueError, comtypes.COMError): 539 | pass 540 | 541 | def __str__(self): 542 | title_bar = '-- ' + str(self.display_name) + ' ' 543 | title_bar += '-' * (63 - len(title_bar)) 544 | res = [ 545 | title_bar, 546 | 'description: ' + str(self.description), 547 | 'version: ' + str(self.version), 548 | 'id: ' + str(self.id), 549 | 'name: ' + str(self.name), 550 | 'display name: ' + str(self.display_name), 551 | 'description: ' + str(self.description), 552 | 'path: ' + str(self.path), 553 | 'version: ' + str(self.version), 554 | 'full version: ' + str(self.full_version), 555 | 'install date: ' + self.install_date.strftime('%c') 556 | ] 557 | return '\n'.join(res) 558 | 559 | 560 | IID_ISetupInstance2 = IID("{89143C9A-05AF-49B0-B717-72E218A2185C}") 561 | 562 | 563 | # Information about an instance of a product. 564 | class ISetupInstance2(ISetupInstance): 565 | _iid_ = IID_ISetupInstance2 566 | 567 | @property 568 | def packages(self) -> Packages: 569 | # noinspection PyUnresolvedReferences 570 | safearray = self.GetPackages() 571 | 572 | _SafeArrayLock(safearray) 573 | 574 | # noinspection PyTypeChecker 575 | packs = ctypes.cast( 576 | safearray.contents.pvData, 577 | POINTER(POINTER(ISetupPackageReference)) 578 | ) 579 | 580 | cPackages = safearray.contents.rgsabound[0].cElements 581 | 582 | res = [] 583 | for i in range(cPackages): 584 | p = packs[i] 585 | res.append(p) 586 | 587 | _SafeArrayUnlock(safearray) 588 | res = Packages(res) 589 | return res 590 | 591 | @property 592 | def properties(self): 593 | # noinspection PyUnresolvedReferences 594 | return self.GetProperties() 595 | 596 | @property 597 | def product(self): 598 | """ 599 | version 600 | chip 601 | language 602 | branch 603 | type 604 | unique_id 605 | is_extension 606 | """ 607 | if 'registered' in self.state: 608 | # noinspection PyUnresolvedReferences 609 | return self.GetProduct() 610 | 611 | @property 612 | def state(self): 613 | # noinspection PyUnresolvedReferences 614 | return self.GetState().value 615 | 616 | @property 617 | def product_path(self): 618 | # noinspection PyUnresolvedReferences 619 | return self.GetProductPath() 620 | 621 | @property 622 | def errors(self): 623 | # noinspection PyUnresolvedReferences 624 | errors = self.GetErrors() 625 | try: 626 | return errors.QueryInterface(ISetupErrorState2) 627 | except ValueError: 628 | return errors 629 | 630 | @property 631 | def is_launchable(self): 632 | # noinspection PyUnresolvedReferences 633 | return self.IsLaunchable() 634 | 635 | @property 636 | def is_complete(self): 637 | # noinspection PyUnresolvedReferences 638 | return self.IsComplete() 639 | 640 | @property 641 | def is_prerelease(self): 642 | catalog = self.QueryInterface(ISetupInstanceCatalog) 643 | return catalog.IsPrerelease() # NOQA 644 | 645 | @property 646 | def catalog(self): 647 | return self.QueryInterface(ISetupInstanceCatalog) 648 | 649 | @property 650 | def engine_path(self): 651 | # noinspection PyUnresolvedReferences 652 | return self.GetEnginePath() 653 | 654 | @property 655 | def localised_properties(self): 656 | return self.QueryInterface(ISetupLocalizedProperties) 657 | 658 | def __str__(self): 659 | res = [ 660 | ISetupInstance.__str__(self), 661 | 'product path: ' + str(self.product_path), 662 | 'is launchable: ' + str(self.is_launchable), 663 | 'is complete: ' + str(self.is_complete), 664 | 'is prerelease: ' + str(self.is_prerelease), 665 | 'state: ' + str(self.state), 666 | 'engine path: ' + str(self.engine_path), 667 | 'errors:', 668 | '{errors}', 669 | 'product: ', 670 | '{product}', 671 | 'packages:', 672 | '{packages}', 673 | 'properties:', 674 | '{properties}', 675 | 'catalog:', 676 | '{catalog}', 677 | '-' * 63 678 | ] 679 | 680 | res = '\n'.join(res) 681 | 682 | return res.format( 683 | errors='\n'.join( 684 | ' ' + line 685 | for line in str(self.errors).split('\n') 686 | ), 687 | product='\n'.join( 688 | ' ' + line 689 | for line in str(self.product).split('\n') 690 | ), 691 | packages='\n'.join( 692 | ' ' + line 693 | for line in str(self.packages).split('\n') 694 | ), 695 | properties='\n'.join( 696 | ' ' + line 697 | for line in str(self.properties).split('\n') 698 | ), 699 | catalog='\n'.join( 700 | ' ' + line 701 | for line in str(self.catalog).split('\n') 702 | ) 703 | ) 704 | 705 | 706 | IID_ISetupInstanceCatalog = IID("{9AD8E40F-39A2-40F1-BF64-0A6C50DD9EEB}") 707 | 708 | 709 | # Information about a catalog used to install an instance. 710 | class ISetupInstanceCatalog(IUnknown): 711 | _iid_ = IID_ISetupInstanceCatalog 712 | 713 | @property 714 | def id(self): 715 | for prop in self: 716 | if prop.name == 'id': 717 | return prop.value 718 | 719 | @property 720 | def build_branch(self): 721 | for prop in self: 722 | if prop.name == 'buildBranch': 723 | return prop.value 724 | 725 | @property 726 | def build_version(self): 727 | for prop in self: 728 | if prop.name == 'buildVersion': 729 | return prop.value 730 | 731 | @property 732 | def local_build(self): 733 | for prop in self: 734 | if prop.name == 'localBuild': 735 | return prop.value 736 | 737 | @property 738 | def manifest_name(self): 739 | for prop in self: 740 | if prop.name == 'manifestName': 741 | return prop.value 742 | 743 | @property 744 | def manifest_type(self): 745 | for prop in self: 746 | if prop.name == 'manifestType': 747 | return prop.value 748 | 749 | @property 750 | def product_display_version(self): 751 | for prop in self: 752 | if prop.name == 'productDisplayVersion': 753 | return prop.value 754 | 755 | @property 756 | def product_line(self): 757 | for prop in self: 758 | if prop.name == 'productLine': 759 | return prop.value 760 | 761 | @property 762 | def product_line_version(self): 763 | for prop in self: 764 | if prop.name == 'productLineVersion': 765 | return prop.value 766 | 767 | @property 768 | def product_milestone(self): 769 | for prop in self: 770 | if prop.name == 'productMilestone': 771 | return prop.value 772 | 773 | @property 774 | def product_milestone_is_prerelease(self): 775 | for prop in self: 776 | if prop.name == 'productMilestoneIsPreRelease': 777 | return prop.value 778 | 779 | @property 780 | def product_name(self): 781 | for prop in self: 782 | if prop.name == 'productName': 783 | return prop.value 784 | 785 | @property 786 | def product_patch_version(self): 787 | for prop in self: 788 | if prop.name == 'productPatchVersion': 789 | return prop.value 790 | 791 | @property 792 | def product_prerelease_milestone_suffix(self): 793 | for prop in self: 794 | if prop.name == 'productPreReleaseMilestoneSuffix': 795 | return prop.value 796 | 797 | @property 798 | def product_semantic_version(self): 799 | for prop in self: 800 | if prop.name == 'productSemanticVersion': 801 | return prop.value 802 | 803 | def __iter__(self): 804 | # noinspection PyUnresolvedReferences 805 | for prop in self.GetCatalogInfo(): 806 | yield prop 807 | 808 | def __str__(self): 809 | res = [prop.name + ': ' + str(prop.value) for prop in self] 810 | return '\n'.join(res) 811 | 812 | 813 | IID_ISetupLocalizedProperties = IID("{F4BD7382-FE27-4AB4-B974-9905B2A148B0}") 814 | 815 | 816 | # Provides localized properties of an instance of a product. 817 | class ISetupLocalizedProperties(IUnknown): 818 | _iid_ = IID_ISetupLocalizedProperties 819 | 820 | 821 | IID_IEnumSetupInstances = IID("{6380BCFF-41D3-4B2E-8B2E-BF8A6810C848}") 822 | 823 | 824 | # A enumerator of installed ISetupInstance objects. 825 | class IEnumSetupInstances(IUnknown): 826 | _iid_ = IID_IEnumSetupInstances 827 | 828 | def __iter__(self): 829 | while True: 830 | try: 831 | # noinspection PyUnresolvedReferences 832 | set_instance, num = self.Next(1) 833 | yield set_instance 834 | 835 | except comtypes.COMError: 836 | break 837 | 838 | 839 | IID_ISetupConfiguration = IID("{42843719-DB4C-46C2-8E7C-64F1816EFD5B}") 840 | 841 | 842 | # Gets information about product instances set up on the machine. 843 | class ISetupConfiguration(IUnknown): 844 | _iid_ = IID_ISetupConfiguration 845 | 846 | def __call__(self): 847 | try: 848 | return self.QueryInterface(ISetupConfiguration2) 849 | except (ValueError, OSError, comtypes.COMError): 850 | return self 851 | 852 | def __iter__(self): 853 | # noinspection PyUnresolvedReferences 854 | setup_enum = self.EnumInstances() 855 | helper = self.QueryInterface(ISetupHelper) 856 | 857 | for si in setup_enum: 858 | if not si: 859 | break 860 | 861 | yield si(helper) 862 | 863 | def __str__(self): 864 | res = [] 865 | for instance_config in self: 866 | res += [str(instance_config)] 867 | 868 | return '\n\n\n'.join(res) 869 | 870 | 871 | IID_ISetupConfiguration2 = IID("{26AAB78C-4A60-49D6-AF3B-3C35BC93365D}") 872 | 873 | 874 | # Gets information about product instances. 875 | class ISetupConfiguration2(ISetupConfiguration): 876 | _iid_ = IID_ISetupConfiguration2 877 | 878 | def __iter__(self): 879 | # noinspection PyUnresolvedReferences 880 | setup_enum = self.EnumAllInstances() 881 | helper = self.QueryInterface(ISetupHelper) 882 | 883 | for si in setup_enum: 884 | if not si: 885 | break 886 | 887 | yield si.QueryInterface(ISetupInstance2)(helper) 888 | 889 | 890 | IID_ISetupHelper = IID("{42b21b78-6192-463e-87bf-d577838f1d5c}") 891 | 892 | 893 | class ISetupHelper(IUnknown): 894 | _iid_ = IID_ISetupHelper 895 | 896 | 897 | IID_ISetupErrorState = IID("{46DCCD94-A287-476A-851E-DFBC2FFDBC20}") 898 | 899 | 900 | # Information about the error state of an instance. 901 | class ISetupErrorState(IUnknown): 902 | _iid_ = IID_ISetupErrorState 903 | 904 | @property 905 | def failed_packages(self) -> Packages: 906 | try: 907 | # noinspection PyUnresolvedReferences 908 | safearray = self.GetFailedPackages() 909 | except ValueError: 910 | return Packages([]) 911 | 912 | _SafeArrayLock(safearray) 913 | 914 | # noinspection PyTypeChecker 915 | packs = ctypes.cast( 916 | safearray.contents.pvData, 917 | POINTER(POINTER(ISetupFailedPackageReference)) 918 | ) 919 | 920 | cPackages = safearray.contents.rgsabound[0].cElements 921 | 922 | res = [] 923 | for i in range(cPackages): 924 | p = packs[i] 925 | p = p.QueryInterface(ISetupFailedPackageReference2) 926 | res.append(p) 927 | 928 | _SafeArrayUnlock(safearray) 929 | res = Packages(res) 930 | return res 931 | 932 | @property 933 | def skipped_packages(self) -> Packages: 934 | try: 935 | # noinspection PyUnresolvedReferences 936 | safearray = self.GetSkippedPackages() 937 | except ValueError: 938 | return Packages([]) 939 | 940 | _SafeArrayLock(safearray) 941 | 942 | # noinspection PyTypeChecker 943 | packs = ctypes.cast( 944 | safearray.contents.pvData, 945 | POINTER(POINTER(ISetupFailedPackageReference)) 946 | ) 947 | 948 | cPackages = safearray.contents.rgsabound[0].cElements 949 | 950 | res = [] 951 | for i in range(cPackages): 952 | p = packs[i] 953 | p = p.QueryInterface(ISetupFailedPackageReference2) 954 | res.append(p) 955 | 956 | _SafeArrayUnlock(safearray) 957 | res = Packages(res) 958 | return res 959 | 960 | def __str__(self): 961 | res = ['failed packages: '] 962 | res.extend([ 963 | ' ' + line for line in 964 | str(self.failed_packages).split('\n') 965 | ]) 966 | 967 | res += ['skipped packages: '] 968 | res.extend([ 969 | ' ' + line for line in 970 | str(self.skipped_packages).split('\n') 971 | ]) 972 | 973 | return '\n'.join(res) 974 | 975 | 976 | IID_ISetupErrorState2 = IID("{9871385B-CA69-48F2-BC1F-7A37CBF0B1EF}") 977 | 978 | 979 | # Information about the error state of an instance. 980 | class ISetupErrorState2(ISetupErrorState): 981 | _iid_ = IID_ISetupErrorState2 982 | 983 | @property 984 | def error_log_file_path(self): 985 | # noinspection PyUnresolvedReferences 986 | return self.GetErrorLogFilePath() 987 | 988 | @property 989 | def log_file_path(self): 990 | # noinspection PyUnresolvedReferences 991 | return self.GetLogFilePath() 992 | 993 | def __str__(self): 994 | res = [ 995 | 'error log file path: ' + self.error_log_file_path, 996 | 'log file path: ' + self.log_file_path, 997 | ISetupErrorState.__str__(self) 998 | ] 999 | return '\n'.join(res) 1000 | 1001 | 1002 | IID_ISetupFailedPackageReference = IID( 1003 | "{E73559CD-7003-4022-B134-27DC650B280F}" 1004 | ) 1005 | 1006 | 1007 | # A reference to a failed package. 1008 | class ISetupFailedPackageReference(ISetupPackageReference): 1009 | _iid_ = IID_ISetupFailedPackageReference 1010 | 1011 | 1012 | IID_ISetupFailedPackageReference2 = IID( 1013 | "{0FAD873E-E874-42E3-B268-4FE2F096B9CA}" 1014 | ) 1015 | 1016 | 1017 | # A reference to a failed package. 1018 | class ISetupFailedPackageReference2(ISetupFailedPackageReference): 1019 | _iid_ = IID_ISetupFailedPackageReference2 1020 | 1021 | 1022 | IID_ISetupPropertyStore = IID("{C601C175-A3BE-44BC-91F6-4568D230FC83}") 1023 | 1024 | 1025 | class Property(object): 1026 | 1027 | def __init__(self, name, value): 1028 | self._name = name 1029 | self._value = value 1030 | 1031 | @property 1032 | def name(self): 1033 | return self._name 1034 | 1035 | @property 1036 | def value(self): 1037 | return self._value 1038 | 1039 | def __str__(self): 1040 | return self.name + ': ' + str(self.value) 1041 | 1042 | 1043 | # Provides named properties. 1044 | class ISetupPropertyStore(IUnknown): 1045 | _iid_ = IID_ISetupPropertyStore 1046 | 1047 | @property 1048 | def names(self): 1049 | # noinspection PyUnresolvedReferences 1050 | safearray = self.GetNames() 1051 | 1052 | _SafeArrayLock(safearray) 1053 | 1054 | names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) 1055 | cPackages = safearray.contents.rgsabound[0].cElements 1056 | 1057 | res = [] 1058 | for i in range(cPackages): 1059 | res.append(names[i]) 1060 | 1061 | _SafeArrayUnlock(safearray) 1062 | 1063 | return res 1064 | 1065 | def __iter__(self): 1066 | for n in self.names: 1067 | # noinspection PyUnresolvedReferences 1068 | v = VARIANT() 1069 | 1070 | self.GetValue(n, ctypes.byref(v)) # NOQA 1071 | 1072 | v = v.value 1073 | if isinstance(v, BSTR): 1074 | v = v.value 1075 | 1076 | yield Property(n, v) 1077 | 1078 | def __str__(self): 1079 | return '\n'.join(str(prop) for prop in self) 1080 | 1081 | 1082 | IID_ISetupLocalizedPropertyStore = IID( 1083 | "{5BB53126-E0D5-43DF-80F1-6B161E5C6F6C}" 1084 | ) 1085 | 1086 | 1087 | # Provides localized named properties. 1088 | class ISetupLocalizedPropertyStore(IUnknown): 1089 | _iid_ = IID_ISetupLocalizedPropertyStore 1090 | 1091 | @property 1092 | def names(self): 1093 | # noinspection PyUnresolvedReferences 1094 | safearray = self.GetNames() 1095 | 1096 | _SafeArrayLock(safearray) 1097 | 1098 | names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) 1099 | cPackages = safearray.contents.rgsabound[0].cElements 1100 | 1101 | res = [] 1102 | for i in range(cPackages): 1103 | res.append(names[i]) 1104 | 1105 | _SafeArrayUnlock(safearray) 1106 | 1107 | return res 1108 | 1109 | def __iter__(self): 1110 | for n in self.names: 1111 | # noinspection PyUnresolvedReferences 1112 | v = VARIANT() 1113 | 1114 | self.GetValue(n, ctypes.byref(v)) # NOQA 1115 | 1116 | v = v.value 1117 | if isinstance(v, BSTR): 1118 | v = v.value 1119 | 1120 | yield Property(n.value, v) 1121 | 1122 | def __str__(self): 1123 | return '\n'.join(str(prop) for prop in self) 1124 | 1125 | 1126 | ISetupPackageReference._methods_ = [ 1127 | # Gets the general package identifier. 1128 | COMMETHOD( 1129 | [], 1130 | HRESULT, 1131 | "GetId", 1132 | (['out'], POINTER(BSTR), "pbstrId") 1133 | ), 1134 | # Gets the version of the package. 1135 | COMMETHOD( 1136 | [], 1137 | HRESULT, 1138 | "GetVersion", 1139 | (['out'], POINTER(BSTR), "pbstrVersion") 1140 | 1141 | ), 1142 | # Gets the target process architecture of the package. 1143 | COMMETHOD( 1144 | [], 1145 | HRESULT, 1146 | "GetChip", 1147 | (['out'], POINTER(BSTR), "pbstrChip") 1148 | ), 1149 | # Gets the language and optional region identifier. 1150 | COMMETHOD( 1151 | [], 1152 | HRESULT, 1153 | "GetLanguage", 1154 | (['out'], POINTER(BSTR), "pbstrLanguage") 1155 | ), 1156 | # Gets the build branch of the package. 1157 | COMMETHOD( 1158 | [], 1159 | HRESULT, 1160 | "GetBranch", 1161 | (['out'], POINTER(BSTR), "pbstrBranch") 1162 | ), 1163 | # Gets the type of the package. 1164 | COMMETHOD( 1165 | [], 1166 | HRESULT, 1167 | "GetType", 1168 | (['out'], POINTER(BSTR), "pbstrType") 1169 | ), 1170 | # Gets the unique identifier consisting of all defined tokens. 1171 | COMMETHOD( 1172 | [], 1173 | HRESULT, 1174 | "GetUniqueId", 1175 | (['out'], POINTER(BSTR), "pbstrUniqueId") 1176 | ), 1177 | # Gets a value indicating whether the package refers to 1178 | # an external extension. 1179 | COMMETHOD( 1180 | [], 1181 | HRESULT, 1182 | "GetIsExtension", 1183 | (['out'], POINTER(VARIANT_BOOL), "pfIsExtension") 1184 | ) 1185 | ] 1186 | 1187 | ISetupInstance._methods_ = [ 1188 | # Gets the instance identifier (should match the name of the 1189 | # parent instance directory). 1190 | COMMETHOD( 1191 | [], 1192 | HRESULT, 1193 | "GetInstanceId", 1194 | (['out'], POINTER(BSTR), "pbstrInstanceId") 1195 | ), 1196 | # Gets the local date and time when the installation 1197 | # was originally installed. 1198 | COMMETHOD( 1199 | [], 1200 | HRESULT, 1201 | "GetInstallDate", 1202 | (['out'], LPFILETIME, "pInstallDate") 1203 | ), 1204 | # Gets the unique name of the installation, often 1205 | # indicating the branch and other information used for telemetry. 1206 | COMMETHOD( 1207 | [], 1208 | HRESULT, 1209 | "GetInstallationName", 1210 | (['out'], POINTER(BSTR), "pbstrInstallationName") 1211 | ), 1212 | # Gets the path to the installation root of the product. 1213 | COMMETHOD( 1214 | [], 1215 | HRESULT, 1216 | "GetInstallationPath", 1217 | (['out'], POINTER(BSTR), "pbstrInstallationPath") 1218 | ), 1219 | # Gets the version of the product installed in this instance. 1220 | COMMETHOD( 1221 | [], 1222 | HRESULT, 1223 | "GetInstallationVersion", 1224 | (['out'], POINTER(BSTR), "pbstrInstallationVersion") 1225 | ), 1226 | # Gets the display name (title) of the product installed 1227 | # in this instance. 1228 | COMMETHOD( 1229 | [], 1230 | HRESULT, 1231 | "GetDisplayName", 1232 | (['in'], LCID, "lcid"), 1233 | (['out'], POINTER(BSTR), "pbstrDisplayName") 1234 | ), 1235 | # Gets the description of the product installed in this instance. 1236 | COMMETHOD( 1237 | [], 1238 | HRESULT, 1239 | "GetDescription", 1240 | (['in'], LCID, "lcid"), 1241 | (['out'], POINTER(BSTR), "pbstrDescription") 1242 | ), 1243 | # Resolves the optional relative path to the root path of the instance. 1244 | COMMETHOD( 1245 | [], 1246 | HRESULT, 1247 | "ResolvePath", 1248 | (['in'], LPCOLESTR, "pwszRelativePath"), 1249 | (['out'], POINTER(BSTR), "pbstrAbsolutePath") 1250 | 1251 | ) 1252 | ] 1253 | 1254 | # noinspection PyTypeChecker 1255 | ISetupInstance2._methods_ = [ 1256 | # Gets the state of the instance. 1257 | COMMETHOD( 1258 | [], 1259 | HRESULT, 1260 | "GetState", 1261 | (['out'], POINTER(InstanceState), "pState") 1262 | ), 1263 | # Gets an array of package references registered to the instance. 1264 | COMMETHOD( 1265 | [], 1266 | HRESULT, 1267 | "GetPackages", 1268 | (['out'], POINTER(LPSAFEARRAY), "ppsaPackages") 1269 | ), 1270 | # Gets a pointer to the ISetupPackageReference that represents 1271 | # the registered product. 1272 | COMMETHOD( 1273 | [], 1274 | HRESULT, 1275 | "GetProduct", 1276 | (['out'], POINTER(POINTER(ISetupPackageReference)), "ppPackage") 1277 | ), 1278 | # Gets the relative path to the product application, if available. 1279 | COMMETHOD( 1280 | [], 1281 | HRESULT, 1282 | "GetProductPath", 1283 | (['out'], POINTER(BSTR), "pbstrProductPath") 1284 | ), 1285 | # Gets the error state of the instance, if available. 1286 | COMMETHOD( 1287 | [], 1288 | HRESULT, 1289 | "GetErrors", 1290 | (['out'], POINTER(POINTER(ISetupErrorState)), "ppErrorState") 1291 | ), 1292 | # Gets a value indicating whether the instance can be launched. 1293 | COMMETHOD( 1294 | [], 1295 | HRESULT, 1296 | "IsLaunchable", 1297 | (['out'], POINTER(VARIANT_BOOL), "pfIsLaunchable") 1298 | ), 1299 | # Gets a value indicating whether the instance is complete. 1300 | COMMETHOD( 1301 | [], 1302 | HRESULT, 1303 | "IsComplete", 1304 | (['out'], POINTER(VARIANT_BOOL), "pfIsComplete") 1305 | ), 1306 | # Gets product-specific properties. 1307 | COMMETHOD( 1308 | [], 1309 | HRESULT, 1310 | "GetProperties", 1311 | (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppProperties") 1312 | ), 1313 | # Gets the directory path to the setup engine 1314 | # that installed the instance. 1315 | COMMETHOD( 1316 | [], 1317 | HRESULT, 1318 | "GetEnginePath", 1319 | (['out'], POINTER(BSTR), "pbstrEnginePath") 1320 | ) 1321 | ] 1322 | 1323 | # noinspection PyTypeChecker 1324 | ISetupInstanceCatalog._methods_ = [ 1325 | # Gets catalog information properties. 1326 | COMMETHOD( 1327 | [], 1328 | HRESULT, 1329 | "GetCatalogInfo", 1330 | (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppCatalogInfo") 1331 | ), 1332 | # Gets a value indicating whether the catalog is a prerelease. 1333 | COMMETHOD( 1334 | [], 1335 | HRESULT, 1336 | "IsPrerelease", 1337 | (['out'], POINTER(VARIANT_BOOL), "pfIsPrerelease") 1338 | ) 1339 | ] 1340 | 1341 | # noinspection PyTypeChecker 1342 | ISetupLocalizedProperties._methods_ = [ 1343 | # Gets localized product-specific properties. 1344 | COMMETHOD( 1345 | [], 1346 | HRESULT, 1347 | "GetLocalizedProperties", 1348 | ( 1349 | ['out'], 1350 | POINTER(POINTER(ISetupLocalizedPropertyStore)), 1351 | "ppLocalizedProperties" 1352 | ) 1353 | ), 1354 | # Gets localized channel-specific properties. 1355 | COMMETHOD( 1356 | [], 1357 | HRESULT, 1358 | "GetLocalizedChannelProperties", 1359 | ( 1360 | ['out'], 1361 | POINTER(POINTER(ISetupLocalizedPropertyStore)), 1362 | "ppLocalizedChannelProperties" 1363 | ) 1364 | ) 1365 | ] 1366 | 1367 | # noinspection PyTypeChecker 1368 | IEnumSetupInstances._methods_ = [ 1369 | # Retrieves the next set of product instances in the 1370 | # enumeration sequence. 1371 | COMMETHOD( 1372 | [], 1373 | HRESULT, 1374 | "Next", 1375 | (['in'], ULONG, "celt"), 1376 | (['out'], POINTER(POINTER(ISetupInstance)), "rgelt"), 1377 | (['out'], POINTER(ULONG), "pceltFetched") 1378 | ), 1379 | # Skips the next set of product instances in the enumeration sequence. 1380 | COMMETHOD( 1381 | [], 1382 | HRESULT, 1383 | "Skip", 1384 | (['in'], ULONG, "celt") 1385 | ), 1386 | # Resets the enumeration sequence to the beginning. 1387 | COMMETHOD( 1388 | [], 1389 | HRESULT, 1390 | "Reset" 1391 | ), 1392 | # Creates a new enumeration object in the same state as the current 1393 | # enumeration object: the new object points to the same place in the 1394 | # enumeration sequence. 1395 | COMMETHOD( 1396 | [], 1397 | HRESULT, 1398 | "Clone", 1399 | (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppenum") 1400 | ) 1401 | ] 1402 | 1403 | # noinspection PyTypeChecker 1404 | ISetupConfiguration._methods_ = [ 1405 | # Enumerates all completed product instances installed. 1406 | COMMETHOD( 1407 | [], 1408 | HRESULT, 1409 | "EnumInstances", 1410 | (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") 1411 | ), 1412 | # Gets the instance for the current process path. 1413 | COMMETHOD( 1414 | [], 1415 | HRESULT, 1416 | "GetInstanceForCurrentProcess", 1417 | (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") 1418 | ), 1419 | # Gets the instance for the given path. 1420 | COMMETHOD( 1421 | [], 1422 | HRESULT, 1423 | "GetInstanceForPath", 1424 | (['in'], LPCWSTR, "wzPath"), 1425 | (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") 1426 | ) 1427 | ] 1428 | 1429 | # noinspection PyTypeChecker 1430 | ISetupConfiguration2._methods_ = [ 1431 | # Enumerates all product instances. 1432 | COMMETHOD( 1433 | [], 1434 | HRESULT, 1435 | "EnumAllInstances", 1436 | (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") 1437 | ) 1438 | ] 1439 | 1440 | ISetupHelper._methods_ = [ 1441 | # Parses a dotted quad version string into a 64-bit unsigned integer. 1442 | COMMETHOD( 1443 | [], 1444 | HRESULT, 1445 | "ParseVersion", 1446 | (['in'], LPCOLESTR, "pwszVersion"), 1447 | (['out'], PULONGLONG, "pullVersion") 1448 | ), 1449 | # Parses a dotted quad version string into a 64-bit unsigned integer. 1450 | COMMETHOD( 1451 | [], 1452 | HRESULT, 1453 | "ParseVersionRange", 1454 | (['in'], LPCOLESTR, "pwszVersionRange"), 1455 | (['out'], PULONGLONG, "pullMinVersion"), 1456 | (['out'], PULONGLONG, "pullMaxVersion") 1457 | ) 1458 | ] 1459 | 1460 | ISetupErrorState._methods_ = ( 1461 | # Gets an array of failed package references. 1462 | COMMETHOD( 1463 | [], 1464 | HRESULT, 1465 | "GetFailedPackages", 1466 | (['out'], POINTER(LPSAFEARRAY), "ppsaFailedPackages") 1467 | ), 1468 | # Gets an array of skipped package references. 1469 | COMMETHOD( 1470 | [], 1471 | HRESULT, 1472 | "GetSkippedPackages", 1473 | (['out'], POINTER(LPSAFEARRAY), "ppsaSkippedPackages") 1474 | ) 1475 | ) 1476 | 1477 | ISetupErrorState2._methods_ = ( 1478 | # Gets the path to the error log. 1479 | COMMETHOD( 1480 | [], 1481 | HRESULT, 1482 | "GetErrorLogFilePath", 1483 | (['out'], POINTER(BSTR), "pbstrErrorLogFilePath") 1484 | ), 1485 | # Gets the path to the main setup log. 1486 | COMMETHOD( 1487 | [], 1488 | HRESULT, 1489 | "GetLogFilePath", 1490 | (['out'], POINTER(BSTR), "pbstrLogFilePath") 1491 | ) 1492 | ) 1493 | 1494 | ISetupFailedPackageReference._methods_ = () 1495 | 1496 | ISetupFailedPackageReference2._methods_ = ( 1497 | # Gets the path to the optional package log. 1498 | COMMETHOD( 1499 | [], 1500 | HRESULT, 1501 | "GetLogFilePath", 1502 | (['out'], POINTER(BSTR), "pbstrLogFilePath") 1503 | ), 1504 | # Gets the description of the package failure. 1505 | COMMETHOD( 1506 | [], 1507 | HRESULT, 1508 | "GetDescription", 1509 | (['out'], POINTER(BSTR), "pbstrDescription") 1510 | ), 1511 | # Gets the signature to use for feedback reporting. 1512 | COMMETHOD( 1513 | [], 1514 | HRESULT, 1515 | "GetSignature", 1516 | (['out'], POINTER(BSTR), "pbstrSignature") 1517 | ), 1518 | # Gets the array of details for this package failure. 1519 | COMMETHOD( 1520 | [], 1521 | HRESULT, 1522 | "GetDetails", 1523 | (['out'], POINTER(LPSAFEARRAY), "ppsaDetails") 1524 | ), 1525 | # Gets an array of packages affected by this package failure. 1526 | COMMETHOD( 1527 | [], 1528 | HRESULT, 1529 | "GetAffectedPackages", 1530 | (['out'], POINTER(LPSAFEARRAY), "ppsaAffectedPackages") 1531 | ) 1532 | ) 1533 | 1534 | ISetupPropertyStore._methods_ = ( 1535 | # Gets an array of property names in this property store. 1536 | COMMETHOD( 1537 | [], 1538 | HRESULT, 1539 | "GetNames", 1540 | (['out'], POINTER(LPSAFEARRAY), "ppsaNames") 1541 | ), 1542 | # Gets the value of a named property in this property store. 1543 | COMMETHOD( 1544 | [], 1545 | HRESULT, 1546 | "GetValue", 1547 | (['in'], LPCOLESTR, "pwszName"), 1548 | (['in'], LPVARIANT, "pvtValue") 1549 | ) 1550 | ) 1551 | 1552 | ISetupLocalizedPropertyStore._methods_ = ( 1553 | # Gets an array of property names in this property store. 1554 | COMMETHOD( 1555 | [], 1556 | HRESULT, 1557 | "GetNames", 1558 | (['in'], LCID, "lcid"), 1559 | (['out'], POINTER(LPSAFEARRAY), "ppsaNames") 1560 | ), 1561 | # Gets the value of a named property in this property store. 1562 | COMMETHOD( 1563 | [], 1564 | HRESULT, 1565 | "GetValue", 1566 | (['in'], LPCOLESTR, "pwszName"), 1567 | (['in'], LCID, "lcid"), 1568 | (['out'], LPVARIANT, "pvtValue") 1569 | ) 1570 | ) 1571 | 1572 | CLSID_SetupConfiguration = CLSID("{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}") 1573 | 1574 | 1575 | # This class implements ISetupConfiguration, ISetupConfiguration2 and 1576 | # ISetupHelper. 1577 | class SetupConfiguration(IUnknown): 1578 | _instance_ = None 1579 | _iid_ = CLSID_SetupConfiguration 1580 | 1581 | ISetupConfiguration = ISetupConfiguration 1582 | ISetupConfiguration2 = ISetupConfiguration2 1583 | ISetupHelper = ISetupHelper 1584 | 1585 | # Gets an ISetupConfiguration that provides information about 1586 | # product instances installed on the machine. 1587 | 1588 | # noinspection PyTypeChecker 1589 | _methods_ = [ 1590 | COMMETHOD( 1591 | [], 1592 | HRESULT, 1593 | "GetSetupConfiguration", 1594 | ( 1595 | ['out'], 1596 | POINTER(POINTER(ISetupConfiguration)), 1597 | "ppConfiguration" 1598 | ), 1599 | ([], LPVOID, "pReserved") 1600 | ) 1601 | ] 1602 | 1603 | @classmethod 1604 | def _callback(cls, _): 1605 | cls._instance_ = None 1606 | comtypes.CoUninitialize() 1607 | 1608 | @classmethod 1609 | def GetSetupConfiguration(cls): 1610 | if cls._instance_ is None: 1611 | comtypes.CoInitialize() 1612 | instance = comtypes.CoCreateInstance( 1613 | CLSID_SetupConfiguration, 1614 | ISetupConfiguration, 1615 | comtypes.CLSCTX_ALL 1616 | )() 1617 | 1618 | cls._instance_ = weakref.ref(instance, cls._callback) 1619 | else: 1620 | instance = cls._instance_() 1621 | 1622 | return instance 1623 | 1624 | 1625 | if __name__ == '__main__': 1626 | setup_config = SetupConfiguration.GetSetupConfiguration() 1627 | print(setup_config) 1628 | -------------------------------------------------------------------------------- /pyMSVC/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # ############################################################################# 4 | # 5 | # MIT License 6 | # 7 | # Copyright 2023 Kevin G. Schlosser 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | # THE SOFTWARE. 26 | # 27 | # 28 | # ############################################################################# 29 | 30 | # This tool is used to create an identical build environment to what is created 31 | # when building a Visual Studio project or using any of the vcvars and vsvars 32 | # batch files. There is a similar tool included with SetupTools and it is 33 | # called msvc. The setup tools version is not complete and it is also error 34 | # prone. It does not make an identical build environment. 35 | 36 | import os 37 | import sys 38 | import ctypes 39 | import subprocess 40 | import winreg 41 | import logging 42 | import platform 43 | import sysconfig 44 | from typing import Optional, Union 45 | __version__ = '0.5.3' 46 | 47 | _IS_WIN = sys.platform.startswith('win') 48 | 49 | logger = logging.getLogger(__name__) 50 | 51 | 52 | def _setup_logging(): 53 | logger.setLevel(logging.DEBUG) 54 | formatter = logging.Formatter('%(levelname)s: %(name)s: %(message)s') 55 | logger_handler = logging.StreamHandler() 56 | logger_handler.setFormatter(formatter) 57 | logger.addHandler(logger_handler) 58 | 59 | 60 | for itm in sys.argv: 61 | if itm.startswith('-v') or itm == '--verbose': 62 | _setup_logging() 63 | break 64 | 65 | 66 | if _IS_WIN: 67 | try: 68 | from . import vswhere 69 | except ImportError: 70 | import vswhere 71 | 72 | _HRESULT = ctypes.c_long 73 | _BOOL = ctypes.c_bool 74 | _DWORD = ctypes.c_ulong 75 | _LPCVOID = ctypes.c_void_p 76 | _LPCWSTR = ctypes.c_wchar_p 77 | _LPVOID = ctypes.c_void_p 78 | _UINT = ctypes.c_uint 79 | _INT = ctypes.c_int 80 | _HANDLE = ctypes.c_void_p 81 | _HWND = ctypes.c_void_p 82 | _LPWSTR = ctypes.c_wchar_p 83 | _POINTER = ctypes.POINTER 84 | _CHAR = _INT 85 | _PUINT = _POINTER(_UINT) 86 | _LPDWORD = _POINTER(_DWORD) 87 | 88 | if _IS_WIN: 89 | try: 90 | _vswhere = vswhere.SetupConfiguration.GetSetupConfiguration() 91 | except: # NOQA 92 | _vswhere = None 93 | else: 94 | _vswhere = None 95 | 96 | 97 | # noinspection PyPep8Naming 98 | class _VS_FIXEDFILEINFO(ctypes.Structure): 99 | _fields_ = [ 100 | ("dwSignature", _DWORD), # will be 0xFEEF04BD 101 | ("dwStrucVersion", _DWORD), 102 | ("dwFileVersionMS", _DWORD), 103 | ("dwFileVersionLS", _DWORD), 104 | ("dwProductVersionMS", _DWORD), 105 | ("dwProductVersionLS", _DWORD), 106 | ("dwFileFlagsMask", _DWORD), 107 | ("dwFileFlags", _DWORD), 108 | ("dwFileOS", _DWORD), 109 | ("dwFileType", _DWORD), 110 | ("dwFileSubtype", _DWORD), 111 | ("dwFileDateMS", _DWORD), 112 | ("dwFileDateLS", _DWORD) 113 | ] 114 | 115 | 116 | if _IS_WIN: 117 | _version = ctypes.windll.version 118 | 119 | _GetFileVersionInfoSize = _version.GetFileVersionInfoSizeW 120 | _GetFileVersionInfoSize.restype = _DWORD 121 | _GetFileVersionInfoSize.argtypes = [_LPCWSTR, _LPDWORD] 122 | 123 | _VerQueryValue = _version.VerQueryValueW 124 | _VerQueryValue.restype = _BOOL 125 | _VerQueryValue.argtypes = [_LPCVOID, _LPCWSTR, _POINTER(_LPVOID), _PUINT] 126 | 127 | _GetFileVersionInfo = _version.GetFileVersionInfoW 128 | _GetFileVersionInfo.restype = _BOOL 129 | _GetFileVersionInfo.argtypes = [_LPCWSTR, _DWORD, _DWORD, _LPVOID] 130 | 131 | 132 | def _get_file_version(filename): 133 | dw_len = _GetFileVersionInfoSize(filename, None) 134 | if not dw_len: 135 | raise ctypes.WinError() 136 | 137 | lp_data = (_CHAR * dw_len)() 138 | if not _GetFileVersionInfo( 139 | filename, 140 | 0, 141 | ctypes.sizeof(lp_data), lp_data 142 | ): 143 | raise ctypes.WinError() 144 | 145 | u_len = _UINT() 146 | lpffi = _POINTER(_VS_FIXEDFILEINFO)() 147 | lplp_buffer = ctypes.cast(ctypes.pointer(lpffi), _POINTER(_LPVOID)) 148 | if not _VerQueryValue(lp_data, "\\", lplp_buffer, ctypes.byref(u_len)): 149 | raise ctypes.WinError() 150 | 151 | ffi = lpffi.contents 152 | return ( 153 | ffi.dwFileVersionMS >> 16, 154 | ffi.dwFileVersionMS & 0xFFFF, 155 | ffi.dwFileVersionLS >> 16, 156 | ffi.dwFileVersionLS & 0xFFFF, 157 | ) 158 | 159 | 160 | _CSIDL_PROGRAM_FILES = 0x26 161 | _CSIDL_PROGRAM_FILESX86 = 0x2A 162 | 163 | _SHGFP_TYPE_CURRENT = 0 164 | _MAX_PATH = 260 165 | _CSIDL_FLAG_DONT_VERIFY = 16384 166 | 167 | _shell32 = ctypes.windll.Shell32 168 | 169 | # noinspection PyUnboundLocalVariable 170 | _SHGetFolderPathW = _shell32.SHGetFolderPathW 171 | _SHGetFolderPathW.restype = _HRESULT 172 | _SHGetFolderPathW.argtypes = [_HWND, _INT, _HANDLE, _DWORD, _LPWSTR] 173 | 174 | _buf = ctypes.create_unicode_buffer(_MAX_PATH) 175 | 176 | _SHGetFolderPathW( 177 | 0, 178 | _CSIDL_PROGRAM_FILESX86 | _CSIDL_FLAG_DONT_VERIFY, 179 | 0, 180 | _SHGFP_TYPE_CURRENT, 181 | _buf 182 | ) 183 | 184 | _PROGRAM_FILES_X86 = _buf.value 185 | 186 | _buf = ctypes.create_unicode_buffer(_MAX_PATH) 187 | 188 | _SHGetFolderPathW( 189 | 0, 190 | _CSIDL_PROGRAM_FILES | _CSIDL_FLAG_DONT_VERIFY, 191 | 0, 192 | _SHGFP_TYPE_CURRENT, 193 | _buf 194 | ) 195 | 196 | _PROGRAM_FILES = _buf.value 197 | 198 | del _buf 199 | del _SHGetFolderPathW 200 | del _shell32 201 | del _CSIDL_PROGRAM_FILES 202 | del _CSIDL_PROGRAM_FILESX86 203 | del _SHGFP_TYPE_CURRENT 204 | del _MAX_PATH 205 | del _CSIDL_FLAG_DONT_VERIFY 206 | 207 | else: 208 | def _get_file_version(_): 209 | pass 210 | 211 | 212 | _PROGRAM_FILES_X86 = '' 213 | _PROGRAM_FILES = '' 214 | 215 | _found_cl = {} 216 | 217 | 218 | def _find_cl(path): 219 | if path in _found_cl: 220 | return _found_cl[path] 221 | 222 | for root, dirs, files in os.walk(path): 223 | if 'cl.exe' not in files: 224 | continue 225 | 226 | if 'MSVC' in root: 227 | head, tail = os.path.split(root) 228 | 229 | while head and not head.endswith('MSVC'): 230 | head, tail = os.path.split(head) 231 | 232 | if head: 233 | _found_cl[path] = [[head.split('\\VC\\')[0] + '\\VC', tail]] 234 | else: 235 | _found_cl[path] = [] 236 | else: 237 | root = root.split('\\VC\\')[0] 238 | ver = os.path.split(root)[1] 239 | 240 | _found_cl[path] = [[root + '\\VC', ver.split(' ')[-1]]] 241 | 242 | return _found_cl[path] 243 | 244 | _found_cl[path] = [] 245 | 246 | return [] 247 | 248 | 249 | def _get_program_files_vc(): 250 | pths = [ 251 | os.path.join(_PROGRAM_FILES_X86, f) 252 | for f in os.listdir(_PROGRAM_FILES_X86) 253 | if 'Visual Studio' in f 254 | ] 255 | res = [ 256 | item for pth in pths for item in _find_cl(pth) 257 | ] 258 | 259 | return res 260 | 261 | 262 | def _get_reg_value(path, key, wow6432=False): 263 | d = _read_reg_values(path, wow6432) 264 | if key in d: 265 | return d[key] 266 | 267 | return '' 268 | 269 | 270 | def _read_reg_keys(key, wow6432=False): 271 | if isinstance(key, tuple): 272 | root = key[0] 273 | key = key[1] 274 | else: 275 | root = winreg.HKEY_LOCAL_MACHINE 276 | key = 'SOFTWARE\\Microsoft\\' + key 277 | 278 | try: 279 | if wow6432: 280 | handle = winreg.OpenKey( 281 | root, 282 | key, 283 | 0, 284 | winreg.KEY_READ | winreg.KEY_WOW64_32KEY 285 | ) 286 | else: 287 | handle = winreg.OpenKeyEx(root, key) 288 | except winreg.error: 289 | return [] 290 | res = [] 291 | 292 | for i in range(winreg.QueryInfoKey(handle)[0]): 293 | res += [winreg.EnumKey(handle, i)] 294 | 295 | winreg.CloseKey(handle) 296 | return res 297 | 298 | 299 | def _read_reg_values(key, wow6432=False): 300 | if isinstance(key, tuple): 301 | root = key[0] 302 | key = key[1] 303 | else: 304 | root = winreg.HKEY_LOCAL_MACHINE 305 | key = 'SOFTWARE\\Microsoft\\' + key 306 | 307 | try: 308 | if wow6432: 309 | handle = winreg.OpenKey( 310 | root, 311 | key, 312 | 0, 313 | winreg.KEY_READ | winreg.KEY_WOW64_32KEY 314 | ) 315 | else: 316 | handle = winreg.OpenKeyEx(root, key) 317 | except winreg.error: 318 | return {} 319 | res = {} 320 | for i in range(winreg.QueryInfoKey(handle)[1]): 321 | name, value, _ = winreg.EnumValue(handle, i) 322 | res[_convert_mbcs(name)] = _convert_mbcs(value) 323 | 324 | winreg.CloseKey(handle) 325 | 326 | return res 327 | 328 | 329 | def _convert_mbcs(s): 330 | dec = getattr(s, "decode", None) 331 | if dec is not None: 332 | try: 333 | s = dec("mbcs") 334 | except UnicodeError: 335 | pass 336 | return s 337 | 338 | 339 | def _convert_version(ver): 340 | if isinstance(ver, str): 341 | ver = tuple(int(item) for item in ver.split('.')) 342 | elif isinstance(ver, bytes): 343 | ver = tuple( 344 | int(item) for item in ver.decode('utf-8').split('.') 345 | ) 346 | elif isinstance(ver, int): 347 | ver = (ver,) 348 | elif isinstance(ver, float): 349 | ver = tuple( 350 | int(item) for item in str(ver).split('.') 351 | ) 352 | elif isinstance(ver, list): 353 | ver = tuple(int(item) for item in ver) 354 | 355 | if not isinstance(ver, tuple): 356 | raise TypeError( 357 | 'Version is not correct type({0})'.format(type(ver)) 358 | ) 359 | 360 | ver = '.'.join(str(item) for item in ver) 361 | 362 | return ver 363 | 364 | 365 | # I have separated the environment into several classes 366 | # Environment - the main environment class. 367 | # the environment class is what is going to get used. this handles all of the 368 | # non specific bits of the environment. all of the rest of the classes are 369 | # brought together in the environment to form a complete build environment. 370 | 371 | # NETInfo - Any .NET related environment settings 372 | 373 | # WindowsSDKInfo - Any Windows SDK environment settings 374 | 375 | # VisualStudioInfo - Any VisualStudios environment settings (if applicable) 376 | 377 | # VisualCInfo - Any VisualC environment settings 378 | 379 | # PythonInfo - This class really isnt for environment settings as such. 380 | # It is more of a convenience class. it will get things like a list of the 381 | # includes specific to the python build. the architecture of the version of 382 | # python that is running stuff along those lines. 383 | class PythonInfo(object): 384 | """ 385 | Build information for the version of Python that is running. 386 | """ 387 | 388 | @property 389 | def architecture(self): 390 | """ 391 | System "bitness" 392 | :rtype: str 393 | :return: "x64" or "x86" 394 | """ 395 | return 'x64' if sys.maxsize > 2 ** 32 else 'x86' 396 | 397 | @property 398 | def version(self): 399 | """ 400 | Python version 401 | 402 | :rtype: str 403 | :return: Python version as a str 404 | """ 405 | return '.'.join(str(ver) for ver in sys.version_info) 406 | 407 | @property 408 | def dependency(self): 409 | """ 410 | Python lib file 411 | 412 | :rtype: str 413 | """ 414 | if sys.version_info[:2] >= (3, 13): 415 | if sysconfig.get_config_var("Py_GIL_DISABLED"): 416 | return 'Python{0}{1}t.lib'.format(*sys.version_info[:2]) 417 | 418 | return 'Python{0}{1}.lib'.format(*sys.version_info[:2]) 419 | 420 | @property 421 | def includes(self): 422 | """ 423 | Python include paths 424 | 425 | :rtype: list 426 | :return: list of str 427 | """ 428 | python_path = os.path.dirname(sys.executable) 429 | python_include = os.path.join(python_path, 'include') 430 | 431 | python_includes = [python_include] 432 | for root, dirs, files in os.walk(python_include): 433 | for d in dirs: 434 | python_includes.append(os.path.join(root, d)) 435 | 436 | return python_includes 437 | 438 | @property 439 | def libraries(self): 440 | """ 441 | Python library files 442 | 443 | :rtype: list 444 | :return: list of str 445 | """ 446 | python_path = os.path.dirname(sys.executable) 447 | python_lib = os.path.join(python_path, 'libs') 448 | 449 | python_libs = [python_lib] 450 | for root, dirs, files in os.walk(python_lib): 451 | for d in dirs: 452 | python_libs += [os.path.join(root, d)] 453 | 454 | return python_libs 455 | 456 | def __str__(self): 457 | template = ( 458 | '== Python =====================================================\n' 459 | ' version: {py_version}\n' 460 | ' architecture: {py_architecture}\n' 461 | ' library: {py_dependency}\n' 462 | ' libs: {py_libraries}\n' 463 | ' includes: {py_includes}\n' 464 | ) 465 | return template.format( 466 | py_version=self.version, 467 | py_architecture=self.architecture, 468 | py_dependency=self.dependency, 469 | py_libraries=self.libraries, 470 | py_includes=self.includes 471 | ) 472 | 473 | 474 | _MSVC_BUILD_TO_VERSION = { 475 | 1944: 14.44, 476 | 1943: 14.43, 477 | 1942: 14.42, 478 | 1941: 14.41, 479 | 1940: 14.40, 480 | 1939: 14.39, 481 | 1938: 14.38, 482 | 1937: 14.36, 483 | 1936: 14.36, 484 | 1935: 14.35, 485 | 1934: 14.34, 486 | 1933: 14.33, 487 | 1932: 14.32, 488 | 1931: 14.31, 489 | 1930: 14.30, 490 | 1929: 14.29, 491 | 1928: 14.28, 492 | 1927: 14.27, 493 | 1926: 14.26, 494 | 1925: 14.25, 495 | 1924: 14.24, 496 | 1923: 14.21, 497 | 1922: 14.21, 498 | 1921: 14.21, 499 | 1920: 14.20, 500 | 1916: 14.16, 501 | 1914: 14.14, 502 | 1913: 14.13, 503 | 1912: 14.12, 504 | 1911: 14.11, 505 | 1910: 14.10, 506 | 1900: 14.00, 507 | 1800: 12.00, 508 | 1700: 11.00, 509 | 1600: 10.00, 510 | 1500: 9.00 511 | } 512 | 513 | 514 | class VisualCInfo(object): 515 | """ 516 | Information about the Visual C or Visual Studio installation. 517 | """ 518 | 519 | def __init__( 520 | self, 521 | environ: "Environment", 522 | minimum_c_version: Optional[Union[int, float]] = None, 523 | strict_c_version: Optional[Union[int, float]] = None, 524 | minimum_toolkit_version: Optional[int] = None, 525 | strict_toolkit_version: Optional[int] = None, 526 | vs_version: Optional[Union[str, int]] = None 527 | ): 528 | self.environment = environ 529 | self.platform = environ.platform 530 | self.strict_c_version = strict_c_version 531 | self.__installed_versions = None 532 | self._cpp_installation = None 533 | self._ide_install_directory = None 534 | self._install_directory = None 535 | self._cpp_version = None 536 | self._tools_version = None 537 | self._toolset_version = None 538 | self._msvc_dll_path = None 539 | self._tools_redist_directory = None 540 | self._tools_install_directory = None 541 | self._msbuild_version = None 542 | self._msbuild_path = None 543 | self._product_semantic_version = None 544 | self._devinit_path = None 545 | 546 | self._strict_toolkit_version = strict_toolkit_version 547 | self._minimum_toolkit_version = minimum_toolkit_version 548 | 549 | compiler_version = int( 550 | platform.python_compiler().split('.')[-1].split(' ')[0] 551 | ) 552 | 553 | min_visual_c_version = _MSVC_BUILD_TO_VERSION.get(compiler_version, None) 554 | 555 | if min_visual_c_version is None: 556 | logging.error('Unable to locate Visual C version using ' 557 | 'compiler version "{0}"'.format(compiler_version)) 558 | sys.exit(-1) 559 | 560 | if minimum_c_version is None: 561 | minimum_c_version = min_visual_c_version 562 | 563 | if ( 564 | strict_c_version is not None and 565 | strict_c_version < min_visual_c_version 566 | ): 567 | logging.error('Set strict MSVC version ({0}) is lower than the ' 568 | 'MSVC version that Python has been compiled with ' 569 | '({1})'.format(strict_c_version, min_visual_c_version)) 570 | sys.exit(-1) 571 | 572 | if minimum_c_version < min_visual_c_version: 573 | logging.warning('Set minimum MSVC version ({0}) is lower than the ' 574 | 'MSVC version that Python has been compiled with ' 575 | '({1})'.format(minimum_c_version, min_visual_c_version)) 576 | 577 | if strict_toolkit_version is not None: 578 | strict_toolkit_version = str(strict_toolkit_version / 10.0) 579 | 580 | if minimum_toolkit_version is not None: 581 | minimum_toolkit_version = str(minimum_toolkit_version / 10.0) 582 | 583 | self.minimum_c_version = minimum_c_version 584 | self._strict_toolkit_version = strict_toolkit_version 585 | self._minimum_toolkit_version = minimum_toolkit_version 586 | 587 | if _vswhere is not None: 588 | logger.debug('\n' + str(_vswhere)) 589 | 590 | cpp_id = 'Microsoft.VisualCpp.Tools.Host{0}.Target{1}'.format( 591 | environ.machine_architecture.upper(), 592 | environ.python.architecture.upper() 593 | ) 594 | 595 | tools_id = ( 596 | 'Microsoft.VisualCpp.Premium.Tools.' 597 | 'Host{0}.Target{1}' 598 | ).format( 599 | environ.machine_architecture.upper(), 600 | environ.python.architecture.upper() 601 | ) 602 | 603 | cpp_version = None 604 | cpp_installation = None 605 | 606 | for installation in _vswhere: 607 | if vs_version is not None: 608 | try: 609 | if isinstance(vs_version, str): 610 | display_version = str( 611 | installation.catalog.product_display_version 612 | ) 613 | 614 | if ( 615 | installation.version != vs_version and 616 | display_version != vs_version 617 | ): 618 | continue 619 | else: 620 | product_line_version = int( 621 | installation.catalog.product_line_version 622 | ) 623 | 624 | if product_line_version != vs_version: 625 | continue 626 | 627 | except: # NOQA 628 | continue 629 | 630 | for package in installation.packages.vsix: 631 | if package.id == cpp_id: 632 | if ( 633 | self.strict_c_version is not None and 634 | package != self.strict_c_version 635 | ): 636 | continue 637 | 638 | if ( 639 | self.minimum_c_version is not None and 640 | package < self.minimum_c_version 641 | ): 642 | continue 643 | 644 | if cpp_version is None: 645 | cpp_version = package 646 | cpp_installation = installation 647 | elif package > cpp_version: 648 | cpp_version = package 649 | cpp_installation = installation 650 | 651 | if cpp_installation is not None: 652 | self._cpp_version = cpp_version.version 653 | self._cpp_installation = cpp_installation 654 | 655 | tools_version = None 656 | tools_path = os.path.join(cpp_installation.path, 'VC', 'Tools', 'MSVC') 657 | 658 | for package in cpp_installation.packages.vsix: 659 | if package.id == tools_id: 660 | if not os.path.exists(os.path.join(tools_path, package.version)): 661 | continue 662 | 663 | if strict_toolkit_version is not None: 664 | if package == strict_toolkit_version: 665 | tools_version = package 666 | break 667 | 668 | if minimum_toolkit_version is not None: 669 | if package <= minimum_toolkit_version: 670 | continue 671 | 672 | if tools_version is None: 673 | tools_version = package 674 | elif tools_version > package: 675 | tools_version = package 676 | 677 | if tools_version is None: 678 | for file in os.listdir(tools_path): 679 | if strict_toolkit_version is not None: 680 | tk_version = file[:len(strict_toolkit_version)] 681 | 682 | if tk_version == strict_toolkit_version: 683 | tools_version = file 684 | break 685 | 686 | if minimum_toolkit_version is not None: 687 | tk_version = file[:len(minimum_toolkit_version)] 688 | 689 | if tk_version < minimum_toolkit_version: 690 | continue 691 | 692 | if tools_version is None: 693 | tools_version = file 694 | elif tools_version < file: 695 | tools_version = file 696 | 697 | else: 698 | tools_version = tools_version.version 699 | 700 | if tools_version is not None: 701 | tools_version = tools_version.split('.') 702 | self._toolset_version = ( 703 | 'v{0}{1}'.format(tools_version[0], tools_version[1][0])) 704 | 705 | self._tools_version = '.'.join(tools_version) 706 | 707 | self._tools_install_directory = os.path.join( 708 | tools_path, self._tools_version) 709 | 710 | tools_redist_directory = ( 711 | self._tools_install_directory.replace('Tools', 'Redist')) 712 | 713 | if os.path.exists(tools_redist_directory): 714 | self._tools_redist_directory = tools_redist_directory + '\\' 715 | 716 | msvc_dll_path = os.path.join( 717 | tools_redist_directory, environ.python.architecture, 718 | 'Microsoft.VC{0}.CRT'.format(self._toolset_version[1:])) 719 | 720 | if os.path.exists(msvc_dll_path): 721 | self._msvc_dll_path = msvc_dll_path 722 | 723 | install_directory = os.path.join(cpp_installation.path, 'VC') 724 | if os.path.exists(install_directory): 725 | self._install_directory = install_directory 726 | 727 | msbuild_path = os.path.join(cpp_installation.path, 'MSBuild', 728 | 'Current', 'Bin', 'MSBuild.exe') 729 | 730 | if os.path.exists(msbuild_path): 731 | msbuild_version = _get_file_version(msbuild_path) 732 | self._msbuild_version = ( 733 | '.'.join(str(item) for item in msbuild_version)) 734 | 735 | self._msbuild_path = msbuild_path 736 | 737 | ide_directory = os.path.join(cpp_installation.path, 738 | 'Common7', 'IDE', 'VC') 739 | 740 | if os.path.exists(ide_directory): 741 | self._ide_install_directory = ide_directory 742 | 743 | product_semantic_version = ( 744 | cpp_installation.catalog.product_semantic_version) 745 | 746 | if product_semantic_version is not None: 747 | self._product_semantic_version = ( 748 | product_semantic_version.split('+')[0]) 749 | 750 | devinit_path = os.path.join(cpp_installation.path, 'Common7', 751 | 'Tools', 'devinit', 'devinit.exe') 752 | 753 | if os.path.exists(devinit_path): 754 | self._devinit_path = devinit_path 755 | 756 | @property 757 | def has_ninja(self) -> bool: 758 | """ 759 | Is Ninja installed. 760 | 761 | :rtype: bool 762 | :return: `True`/`False` 763 | """ 764 | ide_path = os.path.split(self.ide_install_directory)[0] 765 | ninja_path = os.path.join(ide_path, 'CommonExtensions', 'Microsoft', 766 | 'CMake', 'Ninja', 'ninja.exe') 767 | 768 | return os.path.exists(ninja_path) 769 | 770 | @property 771 | def has_cmake(self) -> bool: 772 | """ 773 | Is CMAKE installed. 774 | 775 | :rtype: bool 776 | :return: `True`/`False` 777 | """ 778 | ide_path = os.path.split(self.ide_install_directory)[0] 779 | 780 | cmake_path = os.path.join(ide_path, 'CommonExtensions', 'Microsoft', 781 | 'CMake', 'CMake', 'bin', 'cmake.exe') 782 | 783 | return os.path.exists(cmake_path) 784 | 785 | @property 786 | def cmake_paths(self) -> list: 787 | """ 788 | Paths to CMAKE 789 | 790 | Gets added to the PATH environment variable. 791 | 792 | :rtype: list 793 | :return: `list` of `str` 794 | """ 795 | paths = [] 796 | ide_path = os.path.split(self.ide_install_directory)[0] 797 | 798 | cmake_path = os.path.join(ide_path, 'CommonExtensions', 799 | 'Microsoft', 'CMake') 800 | 801 | if os.path.exists(cmake_path): 802 | bin_path = os.path.join(cmake_path, 'CMake', 'bin') 803 | ninja_path = os.path.join(cmake_path, 'Ninja') 804 | if os.path.exists(bin_path): 805 | paths.append(bin_path) 806 | 807 | if os.path.exists(ninja_path): 808 | paths.append(ninja_path) 809 | 810 | return paths 811 | 812 | @property 813 | def cpp_installation(self) -> Union[vswhere.ISetupInstance, vswhere.ISetupInstance2]: 814 | return self._cpp_installation 815 | 816 | @property 817 | def f_sharp_path(self) -> Optional[str]: 818 | """ 819 | Path to F# 820 | 821 | Gets set to the FSHARPINSTALLDIR envioronment variable. 822 | Gets added to the PATH environment variable. 823 | 824 | :rtype: Optional, str 825 | :return: path to F# or `None` 826 | """ 827 | version = float(int(self.version.split('.')[0])) 828 | 829 | reg_path = (winreg.HKEY_LOCAL_MACHINE, 830 | r'SOFTWARE\Microsoft\VisualStudio' 831 | r'\{0:.1f}\Setup\F#'.format(version)) 832 | 833 | f_sharp_path = _get_reg_value(reg_path, 'ProductDir') 834 | if f_sharp_path and os.path.exists(f_sharp_path): 835 | return f_sharp_path 836 | 837 | path = r'C:\Program Files (x86)\Microsoft SDKs\F#' 838 | if os.path.exists(path): 839 | versions = os.listdir(path) 840 | max_ver = 0.0 841 | found_version = '' 842 | 843 | for version in versions: 844 | try: 845 | ver = float(version) 846 | except ValueError: 847 | continue 848 | 849 | if ver > max_ver: 850 | max_ver = ver 851 | found_version = version 852 | 853 | f_sharp_path = os.path.join(path, found_version, 854 | 'Framework', 'v' + found_version) 855 | 856 | if os.path.exists(f_sharp_path): 857 | return f_sharp_path 858 | 859 | install_dir = os.path.split(self.install_directory)[0] 860 | f_sharp_path = os.path.join(install_dir, 'Common7', 'IDE', 'CommonExtensions', 861 | 'Microsoft', 'FSharp', 'Tools') 862 | 863 | if os.path.exists(f_sharp_path): 864 | return f_sharp_path 865 | 866 | @property 867 | def ide_install_directory(self) -> str: 868 | """ 869 | IDE (Visual Studio) installation path 870 | 871 | Gets set as the VCIDEInstallDir environment variable. 872 | 873 | :rtype: str 874 | """ 875 | if self._ide_install_directory is None: 876 | directory = self.install_directory 877 | ide_directory = os.path.abspath(os.path.join(directory, '..')) 878 | 879 | ide_directory = os.path.join(ide_directory, 'Common7', 'IDE', 'VC') 880 | if os.path.exists(ide_directory): 881 | self._ide_install_directory = ide_directory 882 | 883 | return self._ide_install_directory 884 | 885 | @property 886 | def install_directory(self) -> str: 887 | """ 888 | Visual C path 889 | 890 | Gets set as the VCINSTALLDIR environment variable. 891 | Gets added to the PATH environment variable. 892 | 893 | :rtype: str 894 | """ 895 | if self._install_directory is None: 896 | self._install_directory = ( 897 | self._installed_c_paths[self.version]['base']) 898 | 899 | return self._install_directory 900 | 901 | @property 902 | def _installed_c_paths(self): 903 | if self.__installed_versions is None: 904 | self.__installed_versions = {} 905 | 906 | def add(vers): 907 | for base_pth, ver in vers: 908 | if os.path.exists(base_pth): 909 | try: 910 | base_ver = float(int(ver.split('.')[0])) 911 | except ValueError: 912 | ver = ver.replace('vc', '') 913 | base_ver = float( 914 | int(ver.replace('vc', '').split('.')[0])) 915 | 916 | self.__installed_versions[ver] = dict(base=base_pth, 917 | root=base_pth) 918 | self.__installed_versions[base_ver] = dict(base=base_pth, 919 | root=base_pth) 920 | 921 | add(_get_program_files_vc()) 922 | 923 | reg_path = (winreg.HKEY_LOCAL_MACHINE, 924 | r'SOFTWARE\Policies\Microsoft\VisualStudio\Setup') 925 | 926 | vs_path = _get_reg_value(reg_path, 'SharedInstallationPath') 927 | 928 | if vs_path: 929 | vs_path = os.path.split(vs_path)[0] 930 | if os.path.exists(vs_path): 931 | res = [] 932 | 933 | pths = [os.path.join(vs_path, vs_ver) 934 | for vs_ver in os.listdir(vs_path) 935 | if vs_ver.isdigit()] 936 | 937 | res.extend([item for pth in pths for item in _find_cl(pth)]) 938 | 939 | add(res) 940 | 941 | reg_path = (winreg.HKEY_CLASSES_ROOT, 942 | r'Local Settings\Software\Microsoft\Windows\Shell\MuiCache') 943 | 944 | paths = [] 945 | 946 | for key in _read_reg_values(reg_path): 947 | if 'cl.exe' in key: 948 | value = _get_reg_value(reg_path, key) 949 | if 'C++ Compiler Driver' in value: 950 | paths += [key] 951 | 952 | elif 'devenv.exe' in key: 953 | if not os.path.exists(key): 954 | continue 955 | 956 | value = _get_reg_value(reg_path, key) 957 | 958 | if value.startswith('Microsoft Visual Studio '): 959 | 960 | head, tail = os.path.split(key) 961 | while tail != 'Common7' and head: 962 | head, tail = os.path.split(head) 963 | 964 | if head: 965 | add(_find_cl(head)) 966 | 967 | for path in paths: 968 | if not os.path.exists(path): 969 | continue 970 | 971 | if '\\VC\\bin' in path: 972 | version = path.split('\\VC\\bin')[0] 973 | elif '\\bin\\Host' in path: 974 | version = path.split('\\bin\\Host')[0] 975 | else: 976 | continue 977 | 978 | version = os.path.split(version)[1] 979 | version = ( 980 | version.replace('Microsoft Visual Studio', '').strip()) 981 | 982 | base_version = float(int(version.split('.')[0])) 983 | 984 | base_path = path.split('\\VC\\')[0] + '\\VC' 985 | if os.path.exists(os.path.join(base_path, 'include')): 986 | vc_root = base_path 987 | else: 988 | vc_root = path.split('\\bin\\')[0] 989 | 990 | self.__installed_versions[version] = dict(base=base_path, 991 | root=vc_root) 992 | self.__installed_versions[base_version] = dict(base=base_path, 993 | root=vc_root) 994 | 995 | reg_path = (winreg.HKEY_LOCAL_MACHINE, 996 | 'SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7') 997 | 998 | for key in _read_reg_values(reg_path): 999 | try: 1000 | version = float(key) 1001 | except ValueError: 1002 | continue 1003 | 1004 | path = _get_reg_value(reg_path, key) 1005 | 1006 | if ( 1007 | (os.path.exists(path) and version not in self.__installed_versions) or 1008 | version == 15.0 1009 | ): 1010 | 1011 | if version == 15.0: 1012 | version = 14.0 1013 | 1014 | if not os.path.split(path)[1] == 'VC': 1015 | path = os.path.join(path, 'VC') 1016 | 1017 | self.__installed_versions[version] = dict(base=path, 1018 | root=path) 1019 | self.__installed_versions[key] = dict(base=path, 1020 | root=path) 1021 | 1022 | return self.__installed_versions 1023 | 1024 | @property 1025 | def version(self) -> str: 1026 | """ 1027 | Visual C version 1028 | 1029 | Sometimes when building extension in python the version of the compiler 1030 | that was used to compile Python has to also be used to compile an 1031 | extension. I have this system set so it will automatically pick the 1032 | most recent compiler installation. this can be overridden in 2 ways. 1033 | The first way being that the compiler version that built Python has to 1034 | be used. The second way is you can set a minimum compiler version to 1035 | use. 1036 | 1037 | :rtype: str 1038 | :return: found Visual C version 1039 | """ 1040 | if self._cpp_version is None: 1041 | if self.strict_c_version is not None: 1042 | if self.strict_c_version not in self._installed_c_paths: 1043 | raise RuntimeError('No Compatible Visual C version found.') 1044 | 1045 | self._cpp_version = str(self.strict_c_version) 1046 | return self._cpp_version 1047 | 1048 | match = None 1049 | for version in self._installed_c_paths: 1050 | if not isinstance(version, float): 1051 | continue 1052 | 1053 | if version >= self.minimum_c_version: 1054 | if match is None: 1055 | match = version 1056 | elif version < match: 1057 | match = version 1058 | 1059 | if match is None: 1060 | raise RuntimeError('No Compatible Visual C version found.') 1061 | 1062 | self._cpp_version = str(match) 1063 | 1064 | return self._cpp_version 1065 | 1066 | @property 1067 | def tools_version(self) -> str: 1068 | """ 1069 | Build tools version 1070 | 1071 | Gets set into the VCToolsVersion environment variable. 1072 | 1073 | :rtype: str 1074 | """ 1075 | if self._tools_version is None: 1076 | version = os.path.split(self.tools_install_directory)[1] 1077 | if not version.split('.')[-1].isdigit(): 1078 | version = str(self.version) 1079 | 1080 | self._tools_version = version 1081 | 1082 | return self._tools_version 1083 | 1084 | @property 1085 | def toolset_version(self) -> str: 1086 | """ 1087 | The platform toolset gets written to the solution file. this instructs 1088 | the compiler to use the matching MSVCPxxx.dll file. 1089 | 1090 | :rtype: str 1091 | """ 1092 | 1093 | if self._toolset_version is None: 1094 | tools_version = self.tools_version.split('.') 1095 | 1096 | self._toolset_version = 'v{0}{0}'.format( 1097 | tools_version[0], tools_version[1][:1]) 1098 | 1099 | return self._toolset_version 1100 | 1101 | @property 1102 | def msvc_dll_version(self) -> Optional[str]: 1103 | """ 1104 | The Version of the MSVC DLL file that was found 1105 | 1106 | :rtype: Optional, str 1107 | :return: `str` version of the DLL or `None` 1108 | """ 1109 | msvc_dll_path = self.msvc_dll_path 1110 | if not msvc_dll_path: 1111 | return 1112 | 1113 | for f in os.listdir(msvc_dll_path): 1114 | if f.endswith('dll'): 1115 | version = _get_file_version(os.path.join(msvc_dll_path, f)) 1116 | return '.'.join(str(ver) for ver in version) 1117 | 1118 | @property 1119 | def msvc_dll_path(self) -> Optional[str]: 1120 | """ 1121 | Path to the MSVC DLL if found 1122 | 1123 | :rtype: Optional, str 1124 | :return: path to DLL or `None` 1125 | """ 1126 | if self._msvc_dll_path is None: 1127 | x64 = self.platform == 'x64' 1128 | 1129 | toolset_version = self.toolset_version 1130 | 1131 | if toolset_version is None: 1132 | return None 1133 | 1134 | folder_names = ('Microsoft.VC{0}.CRT'.format(toolset_version[1:]),) 1135 | 1136 | redist_path = self.tools_redist_directory 1137 | 1138 | for root, dirs, files in os.walk(redist_path): 1139 | def pass_directory(): 1140 | for item in ('onecore', 'arm', 'spectre'): 1141 | if item in root.lower(): 1142 | return True 1143 | 1144 | return False 1145 | 1146 | if pass_directory(): 1147 | continue 1148 | 1149 | for folder_name in folder_names: 1150 | if folder_name in dirs: 1151 | if x64 and ('amd64' in root or 'x64' in root): 1152 | self._msvc_dll_path = os.path.join(root, folder_name) 1153 | break 1154 | 1155 | elif not x64 and 'amd64' not in root and 'x64' not in root: 1156 | self._msvc_dll_path = os.path.join(root, folder_name) 1157 | break 1158 | 1159 | return self._msvc_dll_path 1160 | 1161 | @property 1162 | def tools_redist_directory(self) -> Optional[str]: 1163 | """ 1164 | Path to the tools redistributable directory. 1165 | 1166 | Gets set into the VCToolsRedistDir environment variable. 1167 | 1168 | :rtype: Optional, str 1169 | :return: path or `None` 1170 | """ 1171 | if self._tools_redist_directory is None: 1172 | tools_install_path = self.tools_install_directory 1173 | 1174 | if 'MSVC' in tools_install_path: 1175 | redist_path = tools_install_path.replace( 1176 | 'Tools', 'Redist') 1177 | 1178 | if ( 1179 | not os.path.exists(redist_path) and 1180 | 'BuildTools' in tools_install_path 1181 | ): 1182 | redist_path = redist_path.replace( 1183 | 'BuildRedist', 'BuildTools') 1184 | else: 1185 | redist_path = os.path.join(tools_install_path, 'Redist') 1186 | 1187 | if not os.path.exists(redist_path): 1188 | redist_path = os.path.split(redist_path)[0] 1189 | tools_version = None 1190 | 1191 | for version in os.listdir(redist_path): 1192 | if not os.path.isdir(os.path.join(redist_path, version)): 1193 | continue 1194 | 1195 | if version.startswith('v'): 1196 | continue 1197 | 1198 | if self._strict_toolkit_version is not None: 1199 | tk_version = ( 1200 | version[:len(self._strict_toolkit_version)]) 1201 | 1202 | if tk_version == self._strict_toolkit_version: 1203 | tools_version = version 1204 | break 1205 | 1206 | if self._minimum_toolkit_version is not None: 1207 | tk_version = ( 1208 | version[:len(self._minimum_toolkit_version)]) 1209 | 1210 | if tk_version < self._minimum_toolkit_version: 1211 | continue 1212 | 1213 | if tools_version is None: 1214 | tools_version = version 1215 | elif tools_version < version: 1216 | tools_version = version 1217 | 1218 | if tools_version is not None: 1219 | self._tools_redist_directory = ( 1220 | os.path.join(redist_path, tools_version)) 1221 | 1222 | else: 1223 | self._tools_redist_directory = '' 1224 | 1225 | else: 1226 | self._tools_redist_directory = redist_path 1227 | 1228 | if ( 1229 | self._tools_redist_directory and 1230 | not self._tools_redist_directory.endswith('\\') 1231 | ): 1232 | self._tools_redist_directory += '\\' 1233 | 1234 | return self._tools_redist_directory 1235 | 1236 | @property 1237 | def tools_install_directory(self) -> Optional[str]: 1238 | """ 1239 | Visual C compiler tools path. 1240 | 1241 | Gets set to the VCToolsInstallDir environment variable. 1242 | Gets added to the INCLUDE, LIBPATH, LIB and PATH environment variables. 1243 | 1244 | :rtype: Optional, str 1245 | :return: Path to the compiler tools or `None` 1246 | """ 1247 | if self._tools_install_directory is None: 1248 | vc_version = float(int(self.version.split('.')[0])) 1249 | 1250 | if vc_version >= 14.0: 1251 | vc_tools_path = self._installed_c_paths[vc_version]['root'] 1252 | else: 1253 | vc_tools_path = self._installed_c_paths[vc_version]['base'] 1254 | 1255 | # lib_path = os.path.join(vc_tools_path, 'lib') 1256 | tools_path = os.path.join(vc_tools_path, 'Tools', 'MSVC') 1257 | 1258 | if os.path.exists(tools_path): 1259 | versions = os.listdir(tools_path) 1260 | tools_version = None 1261 | 1262 | for version in versions: 1263 | if self._strict_toolkit_version is not None: 1264 | tk_version = ( 1265 | version[:len(self._strict_toolkit_version)]) 1266 | 1267 | if tk_version == self._strict_toolkit_version: 1268 | tools_version = version 1269 | break 1270 | 1271 | if self._minimum_toolkit_version is not None: 1272 | tk_version = ( 1273 | version[:len(self._minimum_toolkit_version)]) 1274 | 1275 | if tk_version < self._minimum_toolkit_version: 1276 | continue 1277 | 1278 | if tools_version is None: 1279 | tools_version = version 1280 | elif tools_version < version: 1281 | tools_version = version 1282 | 1283 | if tools_version is not None: 1284 | self._tools_install_directory = ( 1285 | os.path.join(tools_path, tools_version)) 1286 | 1287 | else: 1288 | raise RuntimeError('Unable to locate build tools') 1289 | 1290 | else: 1291 | raise RuntimeError('Unable to locate build tools') 1292 | 1293 | return self._tools_install_directory 1294 | 1295 | @property 1296 | def msbuild_version(self) -> Optional[str]: 1297 | """ 1298 | MSBuild versions are specific to the Visual C version 1299 | 1300 | :rtype: Optional, str 1301 | :return: MSBuild version, `"3.5"`, `"4.0"`, `"12"`, `"14"`, 1302 | `"15"` or `None` 1303 | """ 1304 | if self._msbuild_version is None: 1305 | vc_version = str(float(int(self.version.split('.')[0]))) 1306 | 1307 | if vc_version == 9.0: 1308 | self._msbuild_version = '3.5' 1309 | elif vc_version in (10.0, 11.0): 1310 | self._msbuild_version = '4.0' 1311 | else: 1312 | self._msbuild_version = vc_version 1313 | 1314 | return self._msbuild_version 1315 | 1316 | @property 1317 | def msbuild_path(self) -> Optional[str]: 1318 | """ 1319 | Path to MSBuild 1320 | 1321 | Gets added to the PATH environment variable. 1322 | 1323 | :rtype: Optional, str 1324 | :return: path to MSBuild or `None` 1325 | """ 1326 | if self._msbuild_path is not None: 1327 | program_files = os.environ.get( 1328 | 'ProgramFiles(x86)', 1329 | 'C:\\Program Files (x86)' 1330 | ) 1331 | 1332 | version = float(int(self.version.split('.')[0])) 1333 | 1334 | ms_build_path = os.path.join(program_files, 'MSBuild', 1335 | '{0:.1f}'.format(version), 'bin') 1336 | 1337 | if self.platform == 'x64': 1338 | if os.path.exists(os.path.join(ms_build_path, 'x64')): 1339 | ms_build_path = os.path.join(ms_build_path, 'x64') 1340 | else: 1341 | ms_build_path = os.path.join(ms_build_path, 'amd64') 1342 | 1343 | elif os.path.exists(os.path.join(ms_build_path, 'x86')): 1344 | ms_build_path = os.path.join(ms_build_path, 'x86') 1345 | 1346 | if os.path.exists(ms_build_path): 1347 | self._msbuild_path = ms_build_path 1348 | 1349 | return self._msbuild_path 1350 | 1351 | @property 1352 | def html_help_path(self) -> Optional[str]: 1353 | """ 1354 | Path to HTMLHelp 1355 | 1356 | Gets set to the HTMLHelpDir environment variable. 1357 | 1358 | :rtype: Optional, str 1359 | :return: path to HTMLHelp or `None` 1360 | """ 1361 | reg_path = ( 1362 | winreg.HKEY_LOCAL_MACHINE, 1363 | r'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\hhw.exe') 1364 | 1365 | html_help_path = _get_reg_value(reg_path, 'Path') 1366 | if html_help_path and os.path.exists(html_help_path): 1367 | return html_help_path 1368 | 1369 | if os.path.exists(r'C:\Program Files (x86)\HTML Help Workshop'): 1370 | return r'C:\Program Files (x86)\HTML Help Workshop' 1371 | 1372 | @property 1373 | def path(self) -> list: 1374 | """ 1375 | Locations that need to be added to the "PATH" environment variable 1376 | 1377 | :rtype: list 1378 | :return: `list` of `str` 1379 | """ 1380 | tools_path = self.tools_install_directory 1381 | base_path = os.path.join(tools_path, 'bin') 1382 | 1383 | path = [] 1384 | 1385 | f_sharp_path = self.f_sharp_path 1386 | msbuild_path = self.msbuild_path 1387 | 1388 | ide_base_path = os.path.split(self.install_directory)[0] 1389 | perf_tools_x64_path = os.path.join(ide_base_path, 'Team Tools', 1390 | 'Performance Tools', 'x64') 1391 | 1392 | if os.path.exists(perf_tools_x64_path): 1393 | path += [perf_tools_x64_path] 1394 | 1395 | perf_tools_path = os.path.join(ide_base_path, 'Team Tools', 1396 | 'Performance Tools') 1397 | if os.path.exists(perf_tools_path): 1398 | path += [perf_tools_path] 1399 | 1400 | com7_ide_path = os.path.join(ide_base_path, 'Common7', 'IDE') 1401 | 1402 | vc_packages_path = os.path.join(com7_ide_path, 'VC', 'VCPackages') 1403 | if os.path.exists(vc_packages_path): 1404 | path += [vc_packages_path] 1405 | 1406 | team_explorer_path = os.path.join(com7_ide_path, 'CommonExtensions', 'Microsoft', 1407 | 'TeamFoundation', 'Team Explorer') 1408 | 1409 | if os.path.exists(team_explorer_path): 1410 | path += [team_explorer_path] 1411 | 1412 | intellicode_cli_path = os.path.join(com7_ide_path, 'Extensions', 1413 | 'Microsoft', 'IntelliCode', 'CLI') 1414 | 1415 | if os.path.exists(intellicode_cli_path): 1416 | path += [intellicode_cli_path] 1417 | 1418 | roslyn_path = os.path.join(ide_base_path, 'MSBuild', 'Current', 1419 | 'bin', 'Roslyn') 1420 | if os.path.exists(roslyn_path): 1421 | path += [roslyn_path] 1422 | 1423 | devinit_path = os.path.join(ide_base_path, 'Common7', 'Tools', 'devinit') 1424 | if os.path.exists(devinit_path): 1425 | path += [devinit_path] 1426 | 1427 | vs_path = os.path.split(ide_base_path)[0] 1428 | vs_path, edition = os.path.split(vs_path) 1429 | 1430 | collection_tools_path = os.path.join(vs_path, 'Shared', 'Common', 1431 | 'VSPerfCollectionTools', 'vs' + edition) 1432 | if os.path.exists(collection_tools_path): 1433 | path += [collection_tools_path] 1434 | 1435 | collection_tools_path_x64 = os.path.join(collection_tools_path, 'x64') 1436 | if os.path.exists(collection_tools_path_x64): 1437 | path += [collection_tools_path_x64] 1438 | 1439 | if msbuild_path is not None: 1440 | path += [os.path.split(msbuild_path)[0]] 1441 | 1442 | if f_sharp_path is not None: 1443 | path += [f_sharp_path] 1444 | 1445 | html_help_path = self.html_help_path 1446 | if html_help_path is not None: 1447 | path += [html_help_path] 1448 | 1449 | bin_path = os.path.join(base_path, 'Host' + self.platform, self.platform) 1450 | if not os.path.exists(bin_path): 1451 | if self.platform == 'x64': 1452 | bin_path = os.path.join(base_path, 'x64') 1453 | if not os.path.exists(bin_path): 1454 | bin_path = os.path.join(base_path, 'amd64') 1455 | else: 1456 | bin_path = os.path.join(base_path, 'x86') 1457 | if not os.path.exists(bin_path): 1458 | bin_path = base_path 1459 | 1460 | if os.path.exists(bin_path): 1461 | path += [bin_path] 1462 | 1463 | path += self.cmake_paths 1464 | 1465 | return path 1466 | 1467 | @property 1468 | def lib(self) -> list: 1469 | """ 1470 | Gets added to the LIB environment variable. 1471 | 1472 | :rtype: list 1473 | :return: list of str 1474 | """ 1475 | tools_path = self.tools_install_directory 1476 | path = os.path.join(tools_path, 'lib') 1477 | 1478 | if self.platform == 'x64': 1479 | lib_path = os.path.join(path, 'x64') 1480 | if not os.path.exists(lib_path): 1481 | lib_path = os.path.join(path, 'amd64') 1482 | 1483 | else: 1484 | lib_path = os.path.join(path, 'x86') 1485 | 1486 | if not os.path.exists(lib_path): 1487 | lib_path = path 1488 | 1489 | lib = [] 1490 | if os.path.exists(lib_path): 1491 | lib += [lib_path] 1492 | 1493 | atlmfc_path = self.atlmfc_lib_path 1494 | if atlmfc_path is not None: 1495 | lib += [atlmfc_path] 1496 | 1497 | return lib 1498 | 1499 | @property 1500 | def lib_path(self) -> list: 1501 | """ 1502 | Gets added to the LIBPATH environment variable. 1503 | 1504 | :rtype: list 1505 | :return: list of str 1506 | """ 1507 | tools_path = self.tools_install_directory 1508 | path = os.path.join(tools_path, 'lib') 1509 | 1510 | if self.platform == 'x64': 1511 | lib = os.path.join(path, 'x64') 1512 | if not os.path.exists(lib): 1513 | lib = os.path.join(path, 'amd64') 1514 | else: 1515 | lib = os.path.join(path, 'x86') 1516 | if not os.path.exists(lib): 1517 | lib = path 1518 | 1519 | references_path = os.path.join(lib, 'store', 'references') 1520 | 1521 | lib_path = [] 1522 | if os.path.exists(lib): 1523 | lib_path += [lib] 1524 | 1525 | atlmfc_path = self.atlmfc_lib_path 1526 | 1527 | if atlmfc_path is not None: 1528 | lib_path += [atlmfc_path] 1529 | 1530 | if os.path.exists(references_path): 1531 | lib_path += [references_path] 1532 | else: 1533 | references_path = os.path.join(path, 'x86', 'store', 'references') 1534 | if os.path.exists(references_path): 1535 | lib_path += [references_path] 1536 | 1537 | return lib_path 1538 | 1539 | @property 1540 | def atlmfc_lib_path(self) -> Optional[str]: 1541 | 1542 | """ 1543 | Gets added to the LIBPATH and LIB environment variables 1544 | 1545 | :rtype: Optional, str 1546 | :return: str or `None` 1547 | """ 1548 | atlmfc_path = self.atlmfc_path 1549 | if not atlmfc_path: 1550 | return 1551 | 1552 | atlmfc = os.path.join(atlmfc_path, 'lib') 1553 | if self.platform == 'x64': 1554 | atlmfc_path = os.path.join(atlmfc, 'x64') 1555 | if not os.path.exists(atlmfc_path): 1556 | atlmfc_path = os.path.join(atlmfc, 'amd64') 1557 | else: 1558 | atlmfc_path = os.path.join(atlmfc, 'x86') 1559 | if not os.path.exists(atlmfc_path): 1560 | atlmfc_path = atlmfc 1561 | 1562 | if os.path.exists(atlmfc_path): 1563 | return atlmfc_path 1564 | 1565 | @property 1566 | def atlmfc_path(self) -> Optional[str]: 1567 | """ 1568 | Path to atlmfc directory 1569 | 1570 | :rtype: Optional, str 1571 | :return: str o `None` 1572 | """ 1573 | tools_path = self.tools_install_directory 1574 | atlmfc_path = os.path.join(tools_path, 'ATLMFC') 1575 | 1576 | if os.path.exists(atlmfc_path): 1577 | return atlmfc_path 1578 | 1579 | @property 1580 | def atlmfc_include_path(self) -> Optional[str]: 1581 | """ 1582 | Gets added to the INCLUDE environment variable. 1583 | 1584 | :rtype: Optional str 1585 | :return: str or `None` 1586 | """ 1587 | atlmfc_path = self.atlmfc_path 1588 | if atlmfc_path is None: 1589 | return 1590 | 1591 | atlmfc_include_path = os.path.join( 1592 | atlmfc_path, 1593 | 'include' 1594 | ) 1595 | if os.path.exists(atlmfc_include_path): 1596 | return atlmfc_include_path 1597 | 1598 | @property 1599 | def include(self) -> list: 1600 | """ 1601 | Gets set to the INCLUDE environment variable. 1602 | 1603 | :rtype: list 1604 | :return: list of str 1605 | """ 1606 | tools_path = self.tools_install_directory 1607 | include_path = os.path.join(tools_path, 'include') 1608 | atlmfc_path = self.atlmfc_include_path 1609 | 1610 | include = [] 1611 | if os.path.exists(include_path): 1612 | include += [include_path] 1613 | 1614 | if atlmfc_path is not None: 1615 | include += [atlmfc_path] 1616 | return include 1617 | 1618 | def __iter__(self): 1619 | ide_install_directory = self.ide_install_directory 1620 | tools_install_directory = self.tools_install_directory 1621 | install_directory = self.install_directory 1622 | 1623 | if ide_install_directory: 1624 | ide_install_directory += '\\' 1625 | 1626 | if tools_install_directory: 1627 | tools_install_directory += '\\' 1628 | 1629 | if install_directory: 1630 | install_directory += '\\' 1631 | 1632 | env = dict( 1633 | VCIDEInstallDir=ide_install_directory, 1634 | VCToolsVersion=self.tools_version, 1635 | VCToolsInstallDir=tools_install_directory, 1636 | VCINSTALLDIR=install_directory, 1637 | VCToolsRedistDir=self.tools_redist_directory, 1638 | Path=self.path, 1639 | LIB=self.lib, 1640 | INCLUDE=self.include, 1641 | LIBPATH=self.lib_path, 1642 | FSHARPINSTALLDIR=self.f_sharp_path 1643 | ) 1644 | 1645 | html_help = self.html_help_path 1646 | 1647 | if html_help is not None: 1648 | env['HTMLHelpDir'] = html_help 1649 | 1650 | if self._product_semantic_version is not None: 1651 | env['VSCMD_VER'] = self._product_semantic_version 1652 | 1653 | if self._devinit_path is not None: 1654 | env['__devinit_path'] = self._devinit_path 1655 | 1656 | for key, value in env.items(): 1657 | if value is not None and value: 1658 | if isinstance(value, list): 1659 | value = os.pathsep.join(value) 1660 | yield key, str(value) 1661 | 1662 | def __str__(self): 1663 | template = ( 1664 | '== Visual C ===================================================\n' 1665 | ' version: {visual_c_version}\n' 1666 | ' path: {visual_c_path}\n' 1667 | ' has cmake: {has_cmake}\n' 1668 | ' has ninja: {has_ninja}\n' 1669 | '\n' 1670 | ' -- Tools ---------------------------------------------------\n' 1671 | ' version: {tools_version}\n' 1672 | ' path: {tools_install_path}\n' 1673 | ' redist path: {vc_tools_redist_path}\n' 1674 | ' -- F# ------------------------------------------------------\n' 1675 | ' path: {f_sharp_path}\n' 1676 | ' -- DLL -----------------------------------------------------\n' 1677 | ' version: {platform_toolset}-{msvc_dll_version}\n' 1678 | ' path: {msvc_dll_path}\n' 1679 | '\n' 1680 | '== MSBuild ====================================================\n' 1681 | ' version: {msbuild_version}\n' 1682 | ' path: {msbuild_path}\n' 1683 | '\n' 1684 | '== HTML Help ==================================================\n' 1685 | ' path: {html_help_path}\n' 1686 | '\n' 1687 | '== ATLMFC =====================================================\n' 1688 | ' path: {atlmfc_path}\n' 1689 | ' include path: {atlmfc_include_path}\n' 1690 | ' lib path: {atlmfc_lib_path}\n' 1691 | ) 1692 | 1693 | return template.format( 1694 | visual_c_version=self.version, 1695 | visual_c_path=self.install_directory, 1696 | has_cmake=self.has_cmake, 1697 | has_ninja=self.has_ninja, 1698 | tools_version=self.tools_version, 1699 | tools_install_path=self.tools_install_directory, 1700 | vc_tools_redist_path=self.tools_redist_directory, 1701 | platform_toolset=self.toolset_version, 1702 | msvc_dll_version=self.msvc_dll_version, 1703 | msvc_dll_path=self.msvc_dll_path, 1704 | msbuild_version=self.msbuild_version, 1705 | msbuild_path=self.msbuild_path, 1706 | f_sharp_path=self.f_sharp_path, 1707 | html_help_path=self.html_help_path, 1708 | atlmfc_lib_path=self.atlmfc_lib_path, 1709 | atlmfc_include_path=self.atlmfc_include_path, 1710 | atlmfc_path=self.atlmfc_path, 1711 | ) 1712 | 1713 | 1714 | class VisualStudioInfo(object): 1715 | 1716 | def __init__( 1717 | self, 1718 | environ: "Environment", 1719 | c_info: VisualCInfo 1720 | ): 1721 | self.environment = environ 1722 | self.__devenv_version = None 1723 | self.c_info = c_info 1724 | self._install_directory = None 1725 | self._dev_env_directory = None 1726 | self._common_tools = None 1727 | 1728 | installation = c_info.cpp_installation 1729 | 1730 | self.__name__ = 'VisualStudioInfo' 1731 | 1732 | if installation is not None: 1733 | if installation.path.endswith('BuildTools'): 1734 | self.__name__ = 'BuildToolsInfo' 1735 | version = installation.version.split('.')[0] 1736 | self.__devenv_version = ( 1737 | str(float(int(version))), 1738 | installation.version 1739 | ) 1740 | 1741 | install_directory = installation.path 1742 | if os.path.exists(install_directory): 1743 | self._install_directory = install_directory 1744 | 1745 | dev_env_directory = os.path.join( 1746 | install_directory, 1747 | os.path.split(installation.product_path)[0] 1748 | ) 1749 | if os.path.exists(dev_env_directory): 1750 | self._dev_env_directory = dev_env_directory 1751 | 1752 | common_tools = os.path.join( 1753 | install_directory, 'Common7', 'Tools' 1754 | ) 1755 | if os.path.exists(common_tools): 1756 | self._common_tools = common_tools + '\\' 1757 | 1758 | @property 1759 | def install_directory(self) -> str: 1760 | """ 1761 | Visual Studio installation directory. 1762 | 1763 | :rtype: str 1764 | """ 1765 | if self._install_directory is None: 1766 | install_dir = os.path.join( 1767 | self.c_info.install_directory, 1768 | '..' 1769 | ) 1770 | 1771 | self._install_directory = ( 1772 | os.path.abspath(install_dir) 1773 | ) 1774 | return self._install_directory 1775 | 1776 | @property 1777 | def dev_env_directory(self) -> str: 1778 | """ 1779 | Directory that devenv.exe is located. 1780 | 1781 | :rtype: str 1782 | """ 1783 | if self._dev_env_directory is None: 1784 | self._dev_env_directory = os.path.join( 1785 | self.install_directory, 1786 | 'Common7', 1787 | 'IDE' 1788 | ) 1789 | 1790 | return self._dev_env_directory 1791 | 1792 | @property 1793 | def common_tools(self) -> str: 1794 | """ 1795 | CommonTools path. 1796 | 1797 | :rtype: str 1798 | """ 1799 | if self._common_tools is None: 1800 | 1801 | common_tools = os.path.join( 1802 | self.install_directory, 1803 | 'Common7', 1804 | 'Tools' 1805 | ) 1806 | if os.path.exists(common_tools): 1807 | self._common_tools = common_tools + '\\' 1808 | else: 1809 | self._common_tools = '' 1810 | 1811 | return self._common_tools 1812 | 1813 | @property 1814 | def path(self) -> list: 1815 | """ 1816 | Gets added to the PATH environment variable. 1817 | 1818 | :rtype: list 1819 | :return: list of str 1820 | """ 1821 | path = [] 1822 | 1823 | dev_env_directory = self.dev_env_directory 1824 | if dev_env_directory: 1825 | path.append(dev_env_directory) 1826 | 1827 | common_tools = self.common_tools 1828 | 1829 | if common_tools: 1830 | path.append(common_tools[:-1]) 1831 | 1832 | collection_tools_dir = _get_reg_value( 1833 | 'VisualStudio\\VSPerf', 1834 | 'CollectionToolsDir' 1835 | ) 1836 | if ( 1837 | collection_tools_dir and 1838 | os.path.exists(collection_tools_dir) 1839 | ): 1840 | path.append(collection_tools_dir) 1841 | 1842 | vs_ide_path = self.dev_env_directory 1843 | 1844 | test_window_path = os.path.join( 1845 | vs_ide_path, 1846 | 'CommonExtensions', 1847 | 'Microsoft', 1848 | 'TestWindow' 1849 | ) 1850 | 1851 | vs_tdb_path = os.path.join( 1852 | vs_ide_path, 1853 | 'VSTSDB', 1854 | 'Deploy' 1855 | ) 1856 | 1857 | if os.path.exists(vs_tdb_path): 1858 | path.append(vs_tdb_path) 1859 | 1860 | if os.path.exists(test_window_path): 1861 | path.append(test_window_path) 1862 | 1863 | return path 1864 | 1865 | @property 1866 | def __version(self): 1867 | if not isinstance(self.__devenv_version, tuple): 1868 | dev_env_dir = self.dev_env_directory 1869 | 1870 | if dev_env_dir is not None: 1871 | command = ''.join([ 1872 | '"', 1873 | os.path.join(dev_env_dir, 'devenv'), 1874 | '" /?\n' 1875 | ]) 1876 | 1877 | proc = subprocess.Popen( 1878 | 'cmd', 1879 | stdout=subprocess.PIPE, 1880 | stderr=subprocess.PIPE, 1881 | stdin=subprocess.PIPE, 1882 | shell=True 1883 | ) 1884 | 1885 | proc.stdin.write(command.encode('utf-8')) 1886 | out, err = proc.communicate() 1887 | proc.stdin.close() 1888 | 1889 | err = err.strip() 1890 | 1891 | if err: 1892 | self.__devenv_version = (None, None) 1893 | return self.__devenv_version 1894 | 1895 | for line in out.decode('utf-8').split('\n'): 1896 | if ' Visual Studio ' in line: 1897 | break 1898 | else: 1899 | self.__devenv_version = (None, None) 1900 | return self.__devenv_version 1901 | 1902 | line = line.rstrip('.').strip() 1903 | line = line.split(' Visual Studio ')[-1] 1904 | 1905 | common_version, version = line.split(' Version ') 1906 | 1907 | self.__devenv_version = (common_version, version) 1908 | else: 1909 | self.__devenv_version = (None, None) 1910 | 1911 | return self.__devenv_version 1912 | 1913 | @property 1914 | def common_version(self) -> Optional[str]: 1915 | """ 1916 | Visual Studio version 1917 | 1918 | Example 1919 | "Microsoft Visual Studio 2022 Version 17.0.5" 1920 | the common version is "2022" 1921 | 1922 | :rtype: Optional, str 1923 | :return: str or `None` 1924 | """ 1925 | return self.__version[0] 1926 | 1927 | @property 1928 | def uncommon_version(self) -> Optional[str]: 1929 | """ 1930 | Visual Studio version 1931 | 1932 | Example 1933 | "Microsoft Visual Studio 2022 Version 17.0.5" 1934 | the uncommon version is "17.0.5" 1935 | 1936 | :rtype: Optional, str 1937 | :return: str or `None` 1938 | """ 1939 | return self.__version[1] 1940 | 1941 | @property 1942 | def version(self) -> Optional[float]: 1943 | """ 1944 | Visual Studio version 1945 | 1946 | Example 1947 | "Microsoft Visual Studio 2022 Version 17.0.5" 1948 | the version is 17.0 1949 | 1950 | :rtype: Optional, float 1951 | :return: float or `None` 1952 | """ 1953 | version = self.uncommon_version 1954 | 1955 | if version is not None: 1956 | return float(int(version.split('.')[0])) 1957 | 1958 | def __iter__(self): 1959 | install_directory = self.install_directory 1960 | dev_env_directory = self.dev_env_directory 1961 | 1962 | if install_directory: 1963 | install_directory += '\\' 1964 | 1965 | if dev_env_directory: 1966 | dev_env_directory += '\\' 1967 | 1968 | env = dict( 1969 | Path=self.path, 1970 | VSINSTALLDIR=install_directory, 1971 | DevEnvDir=dev_env_directory, 1972 | VisualStudioVersion=self.version 1973 | ) 1974 | 1975 | comn_tools = 'VS{0}COMNTOOLS'.format( 1976 | str(int(self.version * 10)) 1977 | ) 1978 | env[comn_tools] = self.common_tools 1979 | 1980 | for key, value in env.items(): 1981 | if value is not None and value: 1982 | if isinstance(value, list): 1983 | value = os.pathsep.join(value) 1984 | yield key, str(value) 1985 | 1986 | def __str__(self): 1987 | installation = self.c_info.cpp_installation 1988 | 1989 | if installation is None: 1990 | return '' 1991 | 1992 | template = ( 1993 | '== {name} \n' 1994 | ' description: {description}\n' 1995 | ' install date: {install_date}\n' 1996 | ' version: {version}\n' 1997 | ' version (friendly): {product_line_version}\n' 1998 | ' display version: {product_display_version}\n' 1999 | ' path: {path}\n' 2000 | ' executable: {product_path}\n' 2001 | ' is complete: {is_complete}\n' 2002 | ' is prerelease: {is_prerelease}\n' 2003 | ' is launchable: {is_launchable}\n' 2004 | ' state: {state}\n' 2005 | ) 2006 | 2007 | name = installation.display_name 2008 | 2009 | if name is None: 2010 | if self.__name__ == 'VisualStudioInfo': 2011 | name = 'Visual Studio' 2012 | else: 2013 | name = 'Build Tools' 2014 | 2015 | description = installation.description 2016 | path = installation.path 2017 | install_date = installation.install_date.strftime('%c') 2018 | version = installation.version 2019 | is_complete = installation.is_complete 2020 | is_prerelease = installation.is_prerelease 2021 | is_launchable = installation.is_launchable 2022 | state = ', '.join(installation.state) 2023 | product_path = os.path.join(path, installation.product_path) 2024 | 2025 | catalog = installation.catalog 2026 | product_display_version = catalog.product_display_version 2027 | product_line_version = catalog.product_line_version 2028 | 2029 | res = template.format( 2030 | name=name, 2031 | description=description, 2032 | install_date=install_date, 2033 | path=path, 2034 | version=version, 2035 | is_complete=is_complete, 2036 | is_prerelease=is_prerelease, 2037 | is_launchable=is_launchable, 2038 | product_path=product_path, 2039 | product_display_version=product_display_version, 2040 | product_line_version=product_line_version, 2041 | state=state 2042 | ) 2043 | 2044 | res = res.split('\n', 1) 2045 | res[0] += '=' * (63 - len(res[0])) 2046 | return '\n'.join(res) 2047 | 2048 | 2049 | class WindowsSDKInfo(object): 2050 | 2051 | def __init__( 2052 | self, 2053 | environ: "Environment", 2054 | c_info: VisualCInfo, 2055 | minimum_sdk_version: Optional[str] = None, 2056 | strict_sdk_version: Optional[str] = None 2057 | ): 2058 | self.environment = environ 2059 | self.c_info = c_info 2060 | self.platform = environ.platform 2061 | self.vc_version = c_info.version 2062 | 2063 | if ( 2064 | strict_sdk_version is not None and 2065 | strict_sdk_version.startswith('10.0') 2066 | ): 2067 | if strict_sdk_version.count('.') == 2: 2068 | strict_sdk_version += '.0' 2069 | 2070 | if ( 2071 | minimum_sdk_version is not None and 2072 | minimum_sdk_version.startswith('10.0') 2073 | ): 2074 | if minimum_sdk_version.count('.') == 2: 2075 | minimum_sdk_version += '.0' 2076 | 2077 | self._minimum_sdk_version = minimum_sdk_version 2078 | self._strict_sdk_version = strict_sdk_version 2079 | 2080 | self._directory = None 2081 | self._sdk_version = None 2082 | self._version = None 2083 | 2084 | @property 2085 | def extension_sdk_directory(self) -> Optional[str]: 2086 | version = self.version 2087 | 2088 | if version.startswith('10'): 2089 | extension_path = os.path.join( 2090 | _PROGRAM_FILES_X86, 2091 | 'Microsoft SDKs', 2092 | 'Windows Kits', 2093 | '10', 2094 | 'ExtensionSDKs' 2095 | ) 2096 | if os.path.exists(extension_path): 2097 | return extension_path 2098 | 2099 | sdk_path = _get_reg_value( 2100 | 'Microsoft SDKs\\Windows\\v' + version, 2101 | 'InstallationFolder' 2102 | ) 2103 | 2104 | if sdk_path: 2105 | sdk_path = sdk_path.replace( 2106 | 'Windows Kits', 2107 | 'Microsoft SDKs\\Windows Kits' 2108 | ) 2109 | extension_path = os.path.join( 2110 | sdk_path[:-1], 2111 | 'Extension SDKs' 2112 | ) 2113 | 2114 | if os.path.exists(extension_path): 2115 | return extension_path 2116 | 2117 | @property 2118 | def lib_version(self) -> str: 2119 | return self.sdk_version 2120 | 2121 | @property 2122 | def ver_bin_path(self) -> str: 2123 | bin_path = self.bin_path[:-1] 2124 | 2125 | version = self.version 2126 | 2127 | if version == '10.0': 2128 | version = self.sdk_version 2129 | 2130 | ver_bin_path = os.path.join(bin_path, version) 2131 | if os.path.exists(ver_bin_path): 2132 | return ver_bin_path 2133 | else: 2134 | return bin_path 2135 | 2136 | @property 2137 | def mssdk(self) -> Optional[str]: 2138 | return self.directory 2139 | 2140 | @property 2141 | def ucrt_version(self) -> Optional[str]: 2142 | if self.version == '10.0': 2143 | sdk_version = self.sdk_version 2144 | else: 2145 | sdk_versions = _read_reg_keys( 2146 | 'Microsoft SDKs\\Windows', 2147 | True 2148 | ) 2149 | if 'v10.0' in sdk_versions: 2150 | sdk_version = _get_reg_value( 2151 | 'Microsoft SDKs\\Windows\\v10.0', 2152 | 'ProductVersion', 2153 | True 2154 | ) 2155 | else: 2156 | return 2157 | 2158 | if sdk_version.endswith('0'): 2159 | return sdk_version 2160 | else: 2161 | return sdk_version[:-1] 2162 | 2163 | @property 2164 | def ucrt_lib_directory(self) -> Optional[str]: 2165 | if self.version == '10.0': 2166 | directory = self.directory 2167 | directory = os.path.join( 2168 | directory, 2169 | 'Lib', 2170 | self.sdk_version, 2171 | 'ucrt', 2172 | self.platform 2173 | ) 2174 | 2175 | else: 2176 | sdk_versions = _read_reg_keys( 2177 | 'Microsoft SDKs\\Windows', 2178 | True 2179 | ) 2180 | if 'v10.0' in sdk_versions: 2181 | sdk_version = _get_reg_value( 2182 | 'Microsoft SDKs\\Windows\\v10.0', 2183 | 'ProductVersion', 2184 | True 2185 | ) 2186 | directory = _get_reg_value( 2187 | 'Microsoft SDKs\\Windows\\v10.0', 2188 | 'InstallationFolder', 2189 | True 2190 | ) 2191 | directory = os.path.join( 2192 | directory, 2193 | 'Lib', 2194 | sdk_version, 2195 | 'ucrt', 2196 | self.platform 2197 | ) 2198 | else: 2199 | return 2200 | 2201 | if os.path.exists(directory): 2202 | if not directory.endswith('\\'): 2203 | return directory + '\\' 2204 | 2205 | return directory 2206 | 2207 | @property 2208 | def ucrt_headers_directory(self) -> Optional[str]: 2209 | if self.version == '10.0': 2210 | directory = self.directory 2211 | directory = os.path.join( 2212 | directory, 2213 | 'Include', 2214 | self.sdk_version, 2215 | 'ucrt' 2216 | ) 2217 | 2218 | else: 2219 | sdk_versions = _read_reg_keys( 2220 | 'Microsoft SDKs\\Windows', 2221 | True 2222 | ) 2223 | if 'v10.0' in sdk_versions: 2224 | sdk_version = _get_reg_value( 2225 | 'Microsoft SDKs\\Windows\\v10.0', 2226 | 'ProductVersion', 2227 | True 2228 | ) 2229 | directory = _get_reg_value( 2230 | 'Microsoft SDKs\\Windows\\v10.0', 2231 | 'InstallationFolder', 2232 | True 2233 | ) 2234 | directory = os.path.join( 2235 | directory, 2236 | 'Include', 2237 | sdk_version, 2238 | 'ucrt' 2239 | ) 2240 | else: 2241 | return 2242 | 2243 | if os.path.exists(directory): 2244 | if not directory.endswith('\\'): 2245 | return directory + '\\' 2246 | 2247 | return directory 2248 | 2249 | @property 2250 | def ucrt_sdk_directory(self) -> Optional[str]: 2251 | directory = self.directory 2252 | 2253 | if self.version != '10.0': 2254 | sdk_versions = _read_reg_keys( 2255 | 'Microsoft SDKs\\Windows', 2256 | True 2257 | ) 2258 | if 'v10.0' in sdk_versions: 2259 | directory = _get_reg_value( 2260 | 'Microsoft SDKs\\Windows\\v10.0', 2261 | 'InstallationFolder', 2262 | True 2263 | ) 2264 | else: 2265 | return 2266 | 2267 | if os.path.exists(directory): 2268 | if not directory.endswith('\\'): 2269 | return directory + '\\' 2270 | 2271 | return directory 2272 | 2273 | @property 2274 | def bin_path(self) -> Optional[str]: 2275 | directory = self.directory 2276 | if directory: 2277 | bin_path = os.path.join( 2278 | self.directory, 2279 | 'bin' 2280 | ) 2281 | 2282 | return bin_path + '\\' 2283 | 2284 | @property 2285 | def lib(self) -> list: 2286 | directory = self.directory 2287 | if not directory: 2288 | return [] 2289 | 2290 | version = self.version 2291 | if version == '10.0': 2292 | version = self.sdk_version 2293 | 2294 | lib = [] 2295 | 2296 | base_lib = os.path.join( 2297 | directory, 2298 | 'lib', 2299 | version, 2300 | ) 2301 | if not os.path.exists(base_lib): 2302 | base_lib = os.path.join( 2303 | directory, 2304 | 'lib' 2305 | ) 2306 | 2307 | if os.path.exists(base_lib): 2308 | if self.platform == 'x64': 2309 | if os.path.exists(os.path.join(base_lib, 'x64')): 2310 | lib += [os.path.join(base_lib, 'x64')] 2311 | 2312 | ucrt = os.path.join(base_lib, 'ucrt', self.platform) 2313 | um = os.path.join(base_lib, 'um', self.platform) 2314 | if not os.path.exists(ucrt): 2315 | ucrt = os.path.join(base_lib, 'ucrt', 'amd64') 2316 | 2317 | if not os.path.exists(um): 2318 | um = os.path.join(base_lib, 'um', 'amd64') 2319 | 2320 | else: 2321 | lib += [base_lib] 2322 | ucrt = os.path.join(base_lib, 'ucrt', self.platform) 2323 | um = os.path.join(base_lib, 'um', self.platform) 2324 | 2325 | if not os.path.exists(ucrt): 2326 | ucrt = os.path.join(base_lib, 'ucrt') 2327 | 2328 | if not os.path.exists(um): 2329 | um = os.path.join(base_lib, 'um') 2330 | 2331 | if os.path.exists(ucrt): 2332 | lib += [ucrt] 2333 | else: 2334 | ucrt = self.ucrt_lib_directory 2335 | if ucrt is not None: 2336 | lib += [ucrt] 2337 | 2338 | if os.path.exists(um): 2339 | lib += [um] 2340 | 2341 | return lib 2342 | 2343 | @property 2344 | def path(self) -> list: 2345 | path = [] 2346 | ver_bin_path = self.ver_bin_path 2347 | 2348 | if self.platform == 'x64': 2349 | bin_path = os.path.join(ver_bin_path, 'x64') 2350 | if not os.path.exists(bin_path): 2351 | bin_path = os.path.join( 2352 | ver_bin_path, 2353 | 'amd64' 2354 | ) 2355 | else: 2356 | bin_path = os.path.join(ver_bin_path, 'x86') 2357 | 2358 | if not os.path.exists(bin_path): 2359 | bin_path = ver_bin_path 2360 | 2361 | if os.path.exists(bin_path): 2362 | path += [bin_path] 2363 | 2364 | bin_path = self.bin_path 2365 | bin_path = os.path.join( 2366 | bin_path, 2367 | 'x64' 2368 | ) 2369 | 2370 | if os.path.exists(bin_path): 2371 | path += [bin_path] 2372 | 2373 | type_script_path = self.type_script_path 2374 | if type_script_path is not None: 2375 | path += [type_script_path] 2376 | 2377 | return path 2378 | 2379 | @property 2380 | def type_script_path(self) -> Optional[str]: 2381 | program_files = os.environ.get( 2382 | 'ProgramFiles(x86)', 2383 | 'C:\\Program Files (x86)' 2384 | ) 2385 | type_script_path = os.path.join( 2386 | program_files, 2387 | 'Microsoft SDKs', 2388 | 'TypeScript' 2389 | ) 2390 | 2391 | if os.path.exists(type_script_path): 2392 | max_ver = 0.0 2393 | for version in os.listdir(type_script_path): 2394 | try: 2395 | version = float(version) 2396 | except ValueError: 2397 | continue 2398 | max_ver = max(max_ver, version) 2399 | 2400 | type_script_path = os.path.join( 2401 | type_script_path, 2402 | str(max_ver) 2403 | ) 2404 | 2405 | if os.path.exists(type_script_path): 2406 | return type_script_path 2407 | 2408 | @property 2409 | def include(self) -> list: 2410 | directory = self.directory 2411 | if self.version == '10.0': 2412 | include_path = os.path.join( 2413 | directory, 2414 | 'include', 2415 | self.sdk_version 2416 | ) 2417 | else: 2418 | include_path = os.path.join( 2419 | directory, 2420 | 'include' 2421 | ) 2422 | 2423 | includes = [include_path] 2424 | 2425 | for path in ('ucrt', 'cppwinrt', 'shared', 'um', 'winrt'): 2426 | pth = os.path.join(include_path, path) 2427 | if os.path.exists(pth): 2428 | includes += [pth] 2429 | elif path == 'ucrt' and self.version != '10.0': 2430 | ucrt = self.ucrt_headers_directory 2431 | if ucrt is not None: 2432 | includes += [ucrt] 2433 | 2434 | gl_include = os.path.join(include_path, 'gl') 2435 | 2436 | if os.path.exists(gl_include): 2437 | includes += [gl_include] 2438 | 2439 | return includes 2440 | 2441 | @property 2442 | def lib_path(self) -> list: 2443 | return self.sdk_lib_path 2444 | 2445 | @property 2446 | def sdk_lib_path(self) -> list: 2447 | directory = self.directory 2448 | version = self.version 2449 | 2450 | if version == '10.0': 2451 | version = self.sdk_version 2452 | 2453 | union_meta_data = os.path.join( 2454 | directory, 2455 | 'UnionMetadata', 2456 | version 2457 | ) 2458 | references = os.path.join( 2459 | directory, 2460 | 'References', 2461 | version 2462 | ) 2463 | 2464 | lib_path = [] 2465 | 2466 | if os.path.exists(union_meta_data): 2467 | lib_path += [union_meta_data] 2468 | 2469 | if os.path.exists(references): 2470 | lib_path += [references] 2471 | 2472 | return lib_path 2473 | 2474 | @property 2475 | def windows_sdks(self) -> list: 2476 | """ 2477 | Windows SDK versions that are compatible with Visual C 2478 | 2479 | :rtype: list 2480 | :return: list of compatible Windows SDK versions 2481 | """ 2482 | ver = int(self.vc_version.split('.')[0]) 2483 | 2484 | sdk_versions = [] 2485 | if ver >= 14: 2486 | sdk_versions.extend(['v10.0']) 2487 | if ver >= 12: 2488 | sdk_versions.extend(['v8.1a', 'v8.1']) 2489 | if ver >= 11: 2490 | sdk_versions.extend(['v8.0a', 'v8.0']) 2491 | if ver >= 10: 2492 | sdk_versions.extend(['v7.1a', 'v7.1', 'v7.0a']) 2493 | 2494 | sdk_versions.extend(['v7.0', 'v6.1', 'v6.0a']) 2495 | 2496 | if ( 2497 | self._minimum_sdk_version is not None and 2498 | self._minimum_sdk_version in sdk_versions 2499 | ): 2500 | index = sdk_versions.index(self._minimum_sdk_version) 2501 | sdk_versions = sdk_versions[:index + 1] 2502 | 2503 | return sdk_versions 2504 | 2505 | @property 2506 | def version(self) -> str: 2507 | """ 2508 | This is used in the solution file to tell the compiler what SDK to use. 2509 | We obtain a list of compatible Windows SDK versions for the 2510 | Visual C version. We check and see if any of the compatible SDK's are 2511 | installed and if so we return that version. 2512 | 2513 | :rtype: str 2514 | :return: Installed Windows SDK version 2515 | """ 2516 | 2517 | if self._version is None: 2518 | sdk_versions = _read_reg_keys('Microsoft SDKs\\Windows', True) 2519 | if self._strict_sdk_version is not None: 2520 | if self._strict_sdk_version.startswith('10.0'): 2521 | keys = _read_reg_keys('Windows Kits\\Installed Roots') 2522 | if self._strict_sdk_version in keys: 2523 | self._version = '10.0' 2524 | return self._version 2525 | 2526 | raise RuntimeError( 2527 | 'Unable to locate Windows SDK ' + 2528 | self._strict_sdk_version 2529 | ) 2530 | 2531 | if 'v' + self._strict_sdk_version in sdk_versions: 2532 | self._version = self._strict_sdk_version 2533 | return self._version 2534 | 2535 | for sdk in self.windows_sdks: 2536 | if sdk in sdk_versions: 2537 | sdk_version = _get_reg_value( 2538 | 'Microsoft SDKs\\Windows\\' + sdk, 2539 | 'ProductVersion', 2540 | True 2541 | ) 2542 | 2543 | if sdk_version.startswith('10.0'): 2544 | while sdk_version.count('.') < 3: 2545 | sdk_version += '.0' 2546 | 2547 | if self._strict_sdk_version is not None: 2548 | if self._strict_sdk_version == sdk_version: 2549 | self._version = sdk[1:] 2550 | return self._version 2551 | 2552 | continue 2553 | 2554 | if self._minimum_sdk_version is not None: 2555 | if self._minimum_sdk_version.count('.') > 1: 2556 | if sdk_version < self._minimum_sdk_version: 2557 | break 2558 | 2559 | self._version = sdk[1:] 2560 | return self._version 2561 | 2562 | if self._strict_sdk_version is not None: 2563 | raise RuntimeError( 2564 | 'Unable to locate Windows SDK version ' + 2565 | self._strict_sdk_version 2566 | ) 2567 | 2568 | if self._minimum_sdk_version is not None: 2569 | raise RuntimeError( 2570 | 'Unable to locate Windows SDK vesion >= ' + 2571 | self._minimum_sdk_version 2572 | ) 2573 | 2574 | raise RuntimeError('Unable to locate Windows SDK') 2575 | 2576 | return self._version 2577 | 2578 | @property 2579 | def sdk_version(self) -> str: 2580 | """ 2581 | This is almost identical to target_platform. Except it returns the 2582 | actual version of the Windows SDK not the truncated version. 2583 | 2584 | :rtype: str 2585 | :return: actual Windows SDK version 2586 | """ 2587 | 2588 | if self._sdk_version is None: 2589 | version = self.version 2590 | 2591 | if self._strict_sdk_version is None: 2592 | self._sdk_version = _get_reg_value( 2593 | 'Microsoft SDKs\\Windows\\v' + version, 2594 | 'ProductVersion', 2595 | True 2596 | ) 2597 | if self._sdk_version.startswith('10.0'): 2598 | while self._sdk_version.count('.') < 3: 2599 | self._sdk_version += '.0' 2600 | 2601 | return self._sdk_version 2602 | 2603 | if self._strict_sdk_version == version: 2604 | self._sdk_version = _get_reg_value( 2605 | 'Microsoft SDKs\\Windows\\v' + version, 2606 | 'ProductVersion', 2607 | True 2608 | ) 2609 | 2610 | if self._sdk_version.startswith('10.0'): 2611 | while self._sdk_version.count('.') < 3: 2612 | self._sdk_version += '.0' 2613 | 2614 | return self._sdk_version 2615 | 2616 | sdk_version = _get_reg_value( 2617 | 'Microsoft SDKs\\Windows\\v' + version, 2618 | 'ProductVersion', 2619 | True 2620 | ) 2621 | 2622 | if sdk_version.startswith('10.0'): 2623 | while sdk_version.count('.') < 3: 2624 | sdk_version += '.0' 2625 | 2626 | if self._strict_sdk_version == sdk_version: 2627 | self._sdk_version = sdk_version 2628 | return self._sdk_version 2629 | 2630 | if self._strict_sdk_version.startswith('10.0'): 2631 | keys = _read_reg_keys('Windows Kits\\Installed Roots') 2632 | if self._strict_sdk_version in keys: 2633 | self._sdk_version = self._strict_sdk_version 2634 | return self._sdk_version 2635 | 2636 | return self._sdk_version 2637 | 2638 | @property 2639 | def directory(self) -> Optional[str]: 2640 | """ 2641 | Path to the Windows SDK version that has been found. 2642 | 2643 | :rtype: Optional, str 2644 | :return: Windows SDK path or `None` 2645 | """ 2646 | 2647 | if self._directory is None: 2648 | version = self.version 2649 | 2650 | if self._strict_sdk_version is None: 2651 | self._directory = _get_reg_value( 2652 | 'Microsoft SDKs\\Windows\\v' + version, 2653 | 'InstallationFolder', 2654 | True 2655 | ) 2656 | return self._directory 2657 | 2658 | if self._strict_sdk_version == version: 2659 | self._directory = _get_reg_value( 2660 | 'Microsoft SDKs\\Windows\\v' + version, 2661 | 'InstallationFolder', 2662 | True 2663 | ) 2664 | return self._directory 2665 | 2666 | sdk_version = _get_reg_value( 2667 | 'Microsoft SDKs\\Windows\\v' + version, 2668 | 'ProductVersion' 2669 | ) 2670 | 2671 | if sdk_version.startswith('10.0'): 2672 | while sdk_version.count('.') < 3: 2673 | sdk_version += '.0' 2674 | 2675 | if self._strict_sdk_version == sdk_version: 2676 | self._directory = _get_reg_value( 2677 | 'Microsoft SDKs\\Windows\\v' + version, 2678 | 'InstallationFolder', 2679 | True 2680 | ) 2681 | return self._directory 2682 | 2683 | if self._strict_sdk_version.startswith('10.0'): 2684 | keys = _read_reg_keys('Windows Kits\\Installed Roots') 2685 | if self._strict_sdk_version in keys: 2686 | self._directory = _get_reg_value( 2687 | 'Microsoft SDKs\\Windows\\v' + version, 2688 | 'InstallationFolder', 2689 | True 2690 | ) 2691 | 2692 | return self._directory 2693 | 2694 | return self._directory 2695 | 2696 | def __iter__(self): 2697 | ver_bin_path = self.ver_bin_path 2698 | directory = self.directory 2699 | 2700 | if ver_bin_path: 2701 | ver_bin_path += '\\' 2702 | 2703 | if directory and not directory.endswith('\\'): 2704 | directory += '\\' 2705 | 2706 | lib_version = self.lib_version 2707 | if not lib_version.endswith('\\'): 2708 | lib_version += '\\' 2709 | 2710 | sdk_version = self.sdk_version 2711 | if not sdk_version.endswith('\\'): 2712 | sdk_version += '\\' 2713 | 2714 | env = dict( 2715 | LIB=self.lib, 2716 | Path=self.path, 2717 | LIBPATH=self.lib_path, 2718 | INCLUDE=self.include, 2719 | UniversalCRTSdkDir=self.ucrt_sdk_directory, 2720 | ExtensionSdkDir=self.extension_sdk_directory, 2721 | WindowsSdkVerBinPath=ver_bin_path, 2722 | UCRTVersion=self.ucrt_version, 2723 | WindowsSDKLibVersion=lib_version, 2724 | WindowsSDKVersion=sdk_version, 2725 | WindowsSdkDir=directory, 2726 | WindowsLibPath=self.lib_path, 2727 | WindowsSdkBinPath=self.bin_path, 2728 | DISTUTILS_USE_SDK=1, 2729 | MSSDK=self.directory 2730 | ) 2731 | 2732 | for key, value in env.items(): 2733 | if value is not None and value: 2734 | if isinstance(value, list): 2735 | value = os.pathsep.join(value) 2736 | yield key, str(value) 2737 | 2738 | def __str__(self): 2739 | template = ( 2740 | '== Windows SDK ================================================\n' 2741 | ' version: {target_platform}\n' 2742 | ' sdk version: {windows_sdk_version}\n' 2743 | ' path: {target_platform_path}\n' 2744 | '\n' 2745 | '== Universal CRT ==============================================\n' 2746 | ' version: {ucrt_version}\n' 2747 | ' path: {ucrt_sdk_directory}\n' 2748 | ' lib directory: {ucrt_lib_directory}\n' 2749 | ' headers directory: {ucrt_headers_directory}\n' 2750 | '\n' 2751 | '== Extension SDK ==============================================\n' 2752 | ' path: {extension_sdk_directory}\n' 2753 | '\n' 2754 | '== TypeScript =================================================\n' 2755 | ' path: {type_script_path}\n' 2756 | ) 2757 | 2758 | return template.format( 2759 | target_platform=self.version, 2760 | windows_sdk_version=self.sdk_version, 2761 | target_platform_path=self.directory, 2762 | extension_sdk_directory=self.extension_sdk_directory, 2763 | ucrt_sdk_directory=self.ucrt_sdk_directory, 2764 | ucrt_headers_directory=self.ucrt_headers_directory, 2765 | ucrt_lib_directory=self.ucrt_lib_directory, 2766 | ucrt_version=self.ucrt_version, 2767 | type_script_path=self.type_script_path, 2768 | ) 2769 | 2770 | 2771 | class NETInfo(object): 2772 | 2773 | def __init__( 2774 | self, 2775 | environ: "Environment", 2776 | c_info: VisualCInfo, 2777 | sdk_version: str, 2778 | minimum_net_version: Optional[str] = None, 2779 | strict_net_version: Optional[str] = None 2780 | ): 2781 | 2782 | self.environment = environ 2783 | self.platform = environ.platform 2784 | self.c_info = c_info 2785 | self.vc_version = c_info.version 2786 | self.sdk_version = sdk_version 2787 | self._minimum_net_version = minimum_net_version 2788 | self._strict_net_version = strict_net_version 2789 | self._version_32 = None 2790 | self._version_64 = None 2791 | 2792 | @property 2793 | def version(self) -> str: 2794 | """ 2795 | .NET Version 2796 | 2797 | :rtype: str 2798 | :return: returns the version associated with the architecture 2799 | """ 2800 | if self.platform == 'x64': 2801 | return self.version_64 2802 | else: 2803 | return self.version_32 2804 | 2805 | @property 2806 | def version_32(self) -> str: 2807 | """ 2808 | .NET 32bit framework version 2809 | 2810 | :rtype: str 2811 | :return: x86 .NET framework version 2812 | """ 2813 | if self._version_32 is None: 2814 | target_framework = None 2815 | installation = self.c_info.cpp_installation 2816 | 2817 | if installation is not None: 2818 | for package in installation.packages.msi: 2819 | if ( 2820 | package.id.startswith('Microsoft.Net') and 2821 | package.id.endswith('TargetingPack') 2822 | ): 2823 | 2824 | if self._strict_net_version is not None: 2825 | if self._strict_net_version == package: 2826 | self._version_32 = 'v' + package.version 2827 | return self._version_32 2828 | else: 2829 | continue 2830 | 2831 | if self._minimum_net_version is not None: 2832 | if package < self._minimum_net_version: 2833 | continue 2834 | 2835 | if target_framework is None: 2836 | target_framework = package 2837 | elif package > target_framework: 2838 | target_framework = package 2839 | 2840 | if target_framework is not None: 2841 | self._version_32 = 'v' + target_framework.version 2842 | return self._version_32 2843 | 2844 | target_framework = _get_reg_value( 2845 | 'VisualStudio\\SxS\\VC7', 2846 | 'FrameworkVer32' 2847 | ) 2848 | 2849 | if target_framework: 2850 | if self._strict_net_version is not None: 2851 | if target_framework[1:] == self._strict_net_version: 2852 | self._version_32 = target_framework 2853 | return self._version_32 2854 | elif self._minimum_net_version is not None: 2855 | if target_framework[1:] >= self._strict_net_version: 2856 | self._version_32 = target_framework 2857 | return self._version_32 2858 | else: 2859 | self._version_32 = target_framework 2860 | return self._version_32 2861 | 2862 | versions = list( 2863 | key for key in _read_reg_keys('.NETFramework\\', True) 2864 | if key.startswith('v') 2865 | ) 2866 | 2867 | target_framework = None 2868 | 2869 | if self._strict_net_version is not None: 2870 | if 'v' + self._strict_net_version in versions: 2871 | self._version_32 = 'v' + self._strict_net_version 2872 | return self._version_32 2873 | else: 2874 | for version in versions: 2875 | if self._minimum_net_version is not None: 2876 | if version[1:] < self._minimum_net_version: 2877 | continue 2878 | 2879 | if target_framework is None: 2880 | target_framework = version[1:] 2881 | elif target_framework < version[1:]: 2882 | target_framework = version[1:] 2883 | 2884 | if target_framework is not None: 2885 | self._version_32 = 'v' + target_framework 2886 | return self._version_32 2887 | 2888 | net_path = os.path.join( 2889 | '%SystemRoot%', 2890 | 'Microsoft.NET', 2891 | 'Framework' 2892 | ) 2893 | net_path = os.path.expandvars(net_path) 2894 | if os.path.exists(net_path): 2895 | versions = [item[1:] for item in os.listdir(net_path)] 2896 | if self._strict_net_version is not None: 2897 | if self._strict_net_version in versions: 2898 | self._version_32 = 'v' + self._strict_net_version 2899 | return self._version_32 2900 | 2901 | raise RuntimeError( 2902 | 'Unable to locate .NET version ' + 2903 | self._strict_net_version 2904 | ) 2905 | 2906 | for version in versions: 2907 | if ( 2908 | self._minimum_net_version is not None and 2909 | version < self._minimum_net_version 2910 | ): 2911 | continue 2912 | 2913 | if target_framework is None: 2914 | target_framework = version 2915 | elif target_framework < version: 2916 | target_framework = version 2917 | 2918 | if target_framework: 2919 | self._version_32 = 'v' + target_framework 2920 | return self._version_32 2921 | 2922 | if self._strict_net_version is not None: 2923 | raise RuntimeError( 2924 | 'Unable to locate .NET version ' + 2925 | self._strict_net_version 2926 | ) 2927 | 2928 | if self._minimum_net_version is not None: 2929 | raise RuntimeError( 2930 | 'Unable to locate .NET version ' + 2931 | self._minimum_net_version + 2932 | ' or above.' 2933 | ) 2934 | 2935 | self._version_32 = '' 2936 | 2937 | return self._version_32 2938 | 2939 | @property 2940 | def version_64(self) -> str: 2941 | """ 2942 | .NET 64bit framework version 2943 | 2944 | :rtype: str 2945 | :return: x64 .NET framework version 2946 | """ 2947 | 2948 | if self._version_64 is None: 2949 | target_framework = None 2950 | installation = self.c_info.cpp_installation 2951 | 2952 | if installation is not None: 2953 | for package in installation.packages.msi: 2954 | if ( 2955 | package.id.startswith('Microsoft.Net') and 2956 | package.id.endswith('TargetingPack') 2957 | ): 2958 | 2959 | if self._strict_net_version is not None: 2960 | if self._strict_net_version == package: 2961 | self._version_64 = 'v' + package.version 2962 | return self._version_64 2963 | else: 2964 | continue 2965 | 2966 | if self._minimum_net_version is not None: 2967 | if package < self._minimum_net_version: 2968 | continue 2969 | 2970 | if target_framework is None: 2971 | target_framework = package 2972 | elif package > target_framework: 2973 | target_framework = package 2974 | 2975 | if target_framework is not None: 2976 | self._version_64 = 'v' + target_framework.version 2977 | return self._version_64 2978 | 2979 | target_framework = _get_reg_value( 2980 | 'VisualStudio\\SxS\\VC7', 2981 | 'FrameworkVer64' 2982 | ) 2983 | 2984 | if target_framework: 2985 | if self._strict_net_version is not None: 2986 | if target_framework[1:] == self._strict_net_version: 2987 | self._version_64 = target_framework 2988 | return self._version_64 2989 | 2990 | elif self._minimum_net_version is not None: 2991 | if target_framework[1:] >= self._strict_net_version: 2992 | self._version_64 = target_framework 2993 | return self._version_64 2994 | 2995 | else: 2996 | self._version_64 = target_framework 2997 | return self._version_64 2998 | 2999 | versions = list( 3000 | key for key in _read_reg_keys('.NETFramework\\', True) 3001 | if key.startswith('v') 3002 | ) 3003 | 3004 | target_framework = None 3005 | 3006 | if self._strict_net_version is not None: 3007 | if 'v' + self._strict_net_version in versions: 3008 | self._version_64 = 'v' + self._strict_net_version 3009 | return self._version_64 3010 | else: 3011 | for version in versions: 3012 | if self._minimum_net_version is not None: 3013 | if version[1:] < self._minimum_net_version: 3014 | continue 3015 | 3016 | if target_framework is None: 3017 | target_framework = version[1:] 3018 | elif target_framework < version[1:]: 3019 | target_framework = version[1:] 3020 | 3021 | if target_framework is not None: 3022 | self._version_64 = 'v' + target_framework 3023 | return self._version_64 3024 | 3025 | net_path = os.path.join( 3026 | '%SystemRoot%', 3027 | 'Microsoft.NET', 3028 | 'Framework64' 3029 | ) 3030 | net_path = os.path.expandvars(net_path) 3031 | if os.path.exists(net_path): 3032 | versions = [item[1:] for item in os.listdir(net_path)] 3033 | if self._strict_net_version is not None: 3034 | if self._strict_net_version in versions: 3035 | self._version_64 = 'v' + self._strict_net_version 3036 | return self._version_64 3037 | 3038 | raise RuntimeError( 3039 | 'Unable to locate .NET version ' + 3040 | self._strict_net_version 3041 | ) 3042 | 3043 | for version in versions: 3044 | if ( 3045 | self._minimum_net_version is not None and 3046 | version < self._minimum_net_version 3047 | ): 3048 | continue 3049 | 3050 | if target_framework is None: 3051 | target_framework = version 3052 | elif target_framework < version: 3053 | target_framework = version 3054 | 3055 | if target_framework: 3056 | self._version_64 = 'v' + target_framework 3057 | return self._version_64 3058 | 3059 | if self._strict_net_version is not None: 3060 | raise RuntimeError( 3061 | 'Unable to locate .NET version ' + 3062 | self._strict_net_version 3063 | ) 3064 | 3065 | if self._minimum_net_version is not None: 3066 | raise RuntimeError( 3067 | 'Unable to locate .NET version ' + 3068 | self._minimum_net_version + 3069 | ' or above.' 3070 | ) 3071 | 3072 | self._version_64 = '' 3073 | 3074 | return self._version_64 3075 | 3076 | @property 3077 | def directory(self) -> str: 3078 | """ 3079 | .NET directory 3080 | 3081 | :rtype: str 3082 | :return: path to .NET 3083 | """ 3084 | if self.platform == 'x64': 3085 | return self.directory_64 3086 | else: 3087 | return self.directory_32 3088 | 3089 | @property 3090 | def directory_32(self) -> str: 3091 | """ 3092 | .NET 32bit path 3093 | 3094 | :rtype: str 3095 | :return: path to x86 .NET 3096 | """ 3097 | directory = _get_reg_value( 3098 | 'VisualStudio\\SxS\\VC7\\', 3099 | 'FrameworkDir32' 3100 | ) 3101 | if not directory: 3102 | directory = os.path.join( 3103 | '%SystemRoot%', 3104 | 'Microsoft.NET', 3105 | 'Framework' 3106 | ) 3107 | directory = os.path.expandvars(directory) 3108 | else: 3109 | directory = directory[:-1] 3110 | 3111 | if os.path.exists(directory): 3112 | return directory 3113 | 3114 | return '' 3115 | 3116 | @property 3117 | def directory_64(self) -> str: 3118 | """ 3119 | .NET 64bit path 3120 | 3121 | :rtype: str 3122 | :return: path to x64 .NET 3123 | """ 3124 | 3125 | directory = _get_reg_value( 3126 | 'VisualStudio\\SxS\\VC7\\', 3127 | 'FrameworkDir64' 3128 | ) 3129 | if not directory: 3130 | directory = os.path.join( 3131 | '%SystemRoot%', 3132 | 'Microsoft.NET', 3133 | 'Framework64' 3134 | ) 3135 | directory = os.path.expandvars(directory) 3136 | else: 3137 | directory = directory[:-1] 3138 | 3139 | if os.path.exists(directory): 3140 | return directory 3141 | 3142 | return '' 3143 | 3144 | @property 3145 | def preferred_bitness(self) -> str: 3146 | """ 3147 | .NET bitness 3148 | 3149 | :rtype: str 3150 | :return: either `"32"` or `"64"` 3151 | """ 3152 | return '32' if self.platform == 'x86' else '64' 3153 | 3154 | @property 3155 | def netfx_sdk_directory(self) -> Optional[str]: 3156 | """ 3157 | Directory where NETFX is located. 3158 | 3159 | :rtype: Optional, str 3160 | :return: path or `None` 3161 | """ 3162 | framework = '.'.join(self.version[1:].split('.')[:2]) 3163 | ver = float(int(self.vc_version.split('.')[0])) 3164 | 3165 | if ver in (9.0, 10.0, 11.0, 12.0): 3166 | key = 'Microsoft SDKs\\Windows\\v{0}\\'.format(self.sdk_version) 3167 | else: 3168 | key = 'Microsoft SDKs\\NETFXSDK\\{0}\\'.format(framework) 3169 | 3170 | net_fx_path = _get_reg_value( 3171 | key, 3172 | 'KitsInstallationFolder', 3173 | wow6432=True 3174 | ) 3175 | 3176 | if net_fx_path and os.path.exists(net_fx_path): 3177 | return net_fx_path 3178 | 3179 | @property 3180 | def net_fx_tools_directory(self) -> Optional[str]: 3181 | """ 3182 | Directory where NETFX tools are located. 3183 | 3184 | :rtype: Optional, str 3185 | :return: path or `None` 3186 | """ 3187 | framework = self.version[1:].split('.')[:2] 3188 | 3189 | if framework[0] == '4': 3190 | net_framework = '40' 3191 | else: 3192 | net_framework = ''.join(framework) 3193 | 3194 | net_fx_key = ( 3195 | 'WinSDK-NetFx{framework}Tools-{platform}' 3196 | ).format( 3197 | framework=''.join(net_framework), 3198 | platform=self.platform 3199 | ) 3200 | 3201 | framework = '.'.join(framework) 3202 | ver = float(int(self.vc_version.split('.')[0])) 3203 | 3204 | if ver in (9.0, 10.0, 11.0, 12.0): 3205 | key = 'Microsoft SDKs\\Windows\\v{0}\\{1}\\'.format( 3206 | self.sdk_version, 3207 | net_fx_key 3208 | ) 3209 | 3210 | if self.sdk_version in ('6.0A', '6.1'): 3211 | key = key.replace(net_fx_key, 'WinSDKNetFxTools') 3212 | 3213 | net_fx_path = _get_reg_value( 3214 | key, 3215 | 'InstallationFolder', 3216 | wow6432=True 3217 | ) 3218 | else: 3219 | key = 'Microsoft SDKs\\NETFXSDK\\{0}\\{1}\\'.format( 3220 | framework, 3221 | net_fx_key 3222 | ) 3223 | net_fx_path = _get_reg_value( 3224 | key, 3225 | 'InstallationFolder', 3226 | wow6432=True 3227 | ) 3228 | 3229 | if net_fx_path and os.path.exists(net_fx_path): 3230 | return net_fx_path 3231 | 3232 | @property 3233 | def add(self) -> str: 3234 | return '__DOTNET_ADD_{0}BIT'.format(self.preferred_bitness) 3235 | 3236 | @property 3237 | def net_tools(self) -> list: 3238 | 3239 | version = float(int(self.vc_version.split('.')[0])) 3240 | if version <= 10.0: 3241 | include32 = True 3242 | include64 = self.platform == 'x64' 3243 | else: 3244 | include32 = self.platform == 'x86' 3245 | include64 = self.platform == 'x64' 3246 | 3247 | tools = [] 3248 | if include32: 3249 | tools += [ 3250 | os.path.join(self.directory_32, self.version_32) 3251 | ] 3252 | if include64: 3253 | tools += [ 3254 | os.path.join(self.directory_64, self.version_64) 3255 | ] 3256 | 3257 | return tools 3258 | 3259 | @property 3260 | def executable_path_x64(self) -> Optional[str]: 3261 | tools_directory = self.net_fx_tools_directory 3262 | if not tools_directory: 3263 | return 3264 | 3265 | if 'NETFX' in tools_directory: 3266 | if 'x64' in tools_directory: 3267 | return tools_directory 3268 | else: 3269 | tools_directory = os.path.join(tools_directory, 'x64') 3270 | if os.path.exists(tools_directory): 3271 | return tools_directory 3272 | 3273 | @property 3274 | def executable_path_x86(self) -> Optional[str]: 3275 | tools_directory = self.net_fx_tools_directory 3276 | if not tools_directory: 3277 | return 3278 | 3279 | if 'NETFX' in tools_directory: 3280 | if 'x64' in tools_directory: 3281 | return ( 3282 | os.path.split(os.path.split(tools_directory)[0])[ 3283 | 0] + '\\' 3284 | ) 3285 | else: 3286 | return tools_directory 3287 | return None 3288 | 3289 | @property 3290 | def lib(self) -> list: 3291 | sdk_directory = self.netfx_sdk_directory 3292 | if not sdk_directory: 3293 | return [] 3294 | 3295 | sdk_directory = os.path.join(sdk_directory, 'lib', 'um') 3296 | 3297 | if self.platform == 'x64': 3298 | lib_dir = os.path.join(sdk_directory, 'x64') 3299 | if not os.path.exists(lib_dir): 3300 | lib_dir = os.path.join(sdk_directory, 'amd64') 3301 | else: 3302 | lib_dir = os.path.join(sdk_directory, 'x86') 3303 | if not os.path.exists(lib_dir): 3304 | lib_dir = sdk_directory 3305 | 3306 | if os.path.exists(lib_dir): 3307 | return [lib_dir] 3308 | 3309 | return [] 3310 | 3311 | @property 3312 | def path(self) -> list: 3313 | path = [] 3314 | directory = self.directory 3315 | 3316 | pth = os.path.join( 3317 | directory, 3318 | self.version 3319 | ) 3320 | 3321 | if os.path.exists(pth): 3322 | path += [pth] 3323 | else: 3324 | match = None 3325 | version = self.version 3326 | versions = [ 3327 | item[1:] for item in os.listdir(directory) 3328 | if item.startswith('v') 3329 | ] 3330 | for ver in versions: 3331 | if version > ver: 3332 | if match is None: 3333 | match = ver 3334 | elif ver > match: 3335 | match = ver 3336 | 3337 | if match is not None: 3338 | pth = os.path.join( 3339 | directory, 3340 | 'v' + match 3341 | ) 3342 | path += [pth] 3343 | 3344 | net_fx_tools = self.net_fx_tools_directory 3345 | if net_fx_tools: 3346 | path += [net_fx_tools] 3347 | 3348 | return path 3349 | 3350 | @property 3351 | def lib_path(self) -> list: 3352 | path = [] 3353 | directory = self.directory 3354 | 3355 | pth = os.path.join( 3356 | directory, 3357 | self.version 3358 | ) 3359 | 3360 | if os.path.exists(pth): 3361 | path += [pth] 3362 | else: 3363 | match = None 3364 | version = self.version 3365 | versions = [ 3366 | item[1:] for item in os.listdir(directory) 3367 | if item.startswith('v') 3368 | ] 3369 | for ver in versions: 3370 | if version > ver: 3371 | if match is None: 3372 | match = ver 3373 | elif ver > match: 3374 | match = ver 3375 | 3376 | if match is not None: 3377 | pth = os.path.join( 3378 | directory, 3379 | 'v' + match 3380 | ) 3381 | path += [pth] 3382 | 3383 | return path 3384 | 3385 | @property 3386 | def include(self) -> list: 3387 | sdk_directory = self.netfx_sdk_directory 3388 | 3389 | if sdk_directory: 3390 | include_dir = os.path.join(sdk_directory, 'include', 'um') 3391 | 3392 | if os.path.exists(include_dir): 3393 | return [include_dir] 3394 | 3395 | return [] 3396 | 3397 | def __iter__(self): 3398 | directory = self.directory 3399 | if directory: 3400 | directory += '\\' 3401 | 3402 | env = dict( 3403 | WindowsSDK_ExecutablePath_x64=self.executable_path_x64, 3404 | WindowsSDK_ExecutablePath_x86=self.executable_path_x86, 3405 | LIB=self.lib, 3406 | Path=self.path, 3407 | LIBPATH=self.lib_path, 3408 | INCLUDE=self.include, 3409 | __DOTNET_PREFERRED_BITNESS=self.preferred_bitness, 3410 | FrameworkDir=directory, 3411 | NETFXSDKDir=self.netfx_sdk_directory, 3412 | ) 3413 | 3414 | version = self.version[1:].split('.') 3415 | if version[0] == '4': 3416 | version = ['4', '0'] 3417 | else: 3418 | version = version[:2] 3419 | 3420 | net_p = 'v' + ('.'.join(version)) 3421 | 3422 | env[self.add] = '1' 3423 | if self.platform == 'x64': 3424 | directory_64 = self.directory_64 3425 | 3426 | if directory_64: 3427 | loc = os.path.join(directory_64, net_p) 3428 | 3429 | if os.path.exists(loc): 3430 | version_64 = loc 3431 | else: 3432 | for p in os.listdir(directory_64): 3433 | if not os.path.isdir(os.path.join(directory_64, p)): 3434 | continue 3435 | 3436 | if p[1:] < net_p[1:]: 3437 | continue 3438 | 3439 | version_64 = p 3440 | break 3441 | else: 3442 | version_64 = self.version_64 3443 | 3444 | directory_64 += '\\' 3445 | else: 3446 | version_64 = None 3447 | 3448 | env['FrameworkDir64'] = directory_64 3449 | env['FrameworkVersion64'] = version_64 3450 | env['FrameworkVersion'] = version_64 3451 | 3452 | else: 3453 | directory_32 = self.directory_32 3454 | 3455 | if directory_32: 3456 | loc = os.path.join(directory_32, net_p) 3457 | 3458 | if not os.path.exists(loc): 3459 | for p in os.listdir(directory_32): 3460 | if not os.path.isdir(os.path.join(directory_32, p)): 3461 | continue 3462 | 3463 | if p[1:] < net_p[1:]: 3464 | continue 3465 | 3466 | version_32 = p 3467 | break 3468 | else: 3469 | version_32 = self.version_64 3470 | 3471 | else: 3472 | version_32 = loc 3473 | 3474 | directory_32 += '\\' 3475 | else: 3476 | version_32 = None 3477 | 3478 | env['FrameworkDir32'] = directory_32 3479 | env['FrameworkVersion32'] = version_32 3480 | env['FrameworkVersion'] = version_32 3481 | 3482 | framework = env['FrameworkVersion'][1:].split('.')[:2] 3483 | 3484 | framework_version_key = ( 3485 | 'Framework{framework}Version'.format(framework=''.join(framework)) 3486 | ) 3487 | env[framework_version_key] = 'v' + '.'.join(framework) 3488 | 3489 | for key, value in env.items(): 3490 | if value is not None and value: 3491 | if isinstance(value, list): 3492 | value = os.pathsep.join(value) 3493 | yield key, str(value) 3494 | 3495 | def __str__(self): 3496 | template = ( 3497 | '== .NET =======================================================\n' 3498 | ' version: {target_framework}\n' 3499 | '\n' 3500 | ' -- x86 -----------------------------------------------------\n' 3501 | ' version: {framework_version_32}\n' 3502 | ' path: {framework_dir_32}\n' 3503 | ' -- x64 -----------------------------------------------------\n' 3504 | ' version: {framework_version_64}\n' 3505 | ' path: {framework_dir_64}\n' 3506 | ' -- NETFX ---------------------------------------------------\n' 3507 | ' path: {net_fx_tools_directory}\n' 3508 | ' x86 exe path: {executable_path_x86}\n' 3509 | ' x64 exe path: {executable_path_x64}\n' 3510 | ) 3511 | return template.format( 3512 | target_framework=self.version, 3513 | framework_version_32=self.version_32, 3514 | framework_dir_32=self.directory_32, 3515 | framework_version_64=self.version_64, 3516 | framework_dir_64=self.directory_64, 3517 | net_fx_tools_directory=self.net_fx_tools_directory, 3518 | executable_path_x64=self.executable_path_x64, 3519 | executable_path_x86=self.executable_path_x86, 3520 | ) 3521 | 3522 | 3523 | class Environment(object): 3524 | _original_environment = {k_: v_ for k_, v_ in os.environ.items()} 3525 | 3526 | def __init__( 3527 | self, 3528 | minimum_c_version: Optional[Union[int, float]] = None, 3529 | strict_c_version: Optional[Union[int, float]] = None, 3530 | minimum_toolkit_version: Optional[int] = None, 3531 | strict_toolkit_version: Optional[int] = None, 3532 | minimum_sdk_version: Optional[str] = None, 3533 | strict_sdk_version: Optional[str] = None, 3534 | minimum_net_version: Optional[str] = None, 3535 | strict_net_version: Optional[str] = None, 3536 | vs_version: Optional[Union[str, int]] = None 3537 | ): 3538 | self.python = PythonInfo() 3539 | 3540 | self.visual_c = VisualCInfo( 3541 | self, 3542 | minimum_c_version, 3543 | strict_c_version, 3544 | minimum_toolkit_version, 3545 | strict_toolkit_version, 3546 | vs_version 3547 | ) 3548 | 3549 | self.visual_studio = VisualStudioInfo( 3550 | self, 3551 | self.visual_c 3552 | ) 3553 | 3554 | self.windows_sdk = WindowsSDKInfo( 3555 | self, 3556 | self.visual_c, 3557 | minimum_sdk_version, 3558 | strict_sdk_version 3559 | ) 3560 | 3561 | self.dot_net = NETInfo( 3562 | self, 3563 | self.visual_c, 3564 | self.windows_sdk.version, 3565 | minimum_net_version, 3566 | strict_net_version 3567 | ) 3568 | 3569 | def reset_environment(self): 3570 | for key in list(os.environ.keys())[:]: 3571 | if key not in self._original_environment: 3572 | del os.environ[key] 3573 | 3574 | os.environ.update(self._original_environment) 3575 | 3576 | @property 3577 | def machine_architecture(self): 3578 | import platform 3579 | return 'x64' if '64' in platform.machine() else 'x86' 3580 | 3581 | @property 3582 | def platform(self): 3583 | """ 3584 | :return: x86 or x64 3585 | """ 3586 | import platform 3587 | 3588 | win_64 = self.machine_architecture == 'x64' 3589 | python_64 = platform.architecture()[0] == '64bit' and win_64 3590 | 3591 | return 'x64' if python_64 else 'x86' 3592 | 3593 | @property 3594 | def configuration(self) -> str: 3595 | """ 3596 | Build configuration 3597 | 3598 | :rtype: str 3599 | :return: `"ReleaseDLL"` or `"DebugDLL"` 3600 | """ 3601 | 3602 | if os.path.splitext(sys.executable)[0].endswith('_d'): 3603 | config = 'Debug' 3604 | else: 3605 | config = 'Release' 3606 | 3607 | return config 3608 | 3609 | def __iter__(self): 3610 | for item in self.build_environment.items(): 3611 | yield item 3612 | 3613 | @property 3614 | def build_environment(self): 3615 | """ 3616 | This would be the work horse. This is where all of the gathered 3617 | information is put into a single container and returned. 3618 | The information is then added to os.environ in order to allow the 3619 | build process to run properly. 3620 | 3621 | List of environment variables generated: 3622 | PATH 3623 | LIBPATH 3624 | LIB 3625 | INCLUDE 3626 | Platform 3627 | FrameworkDir 3628 | FrameworkVersion 3629 | FrameworkDIR32 3630 | FrameworkVersion32 3631 | FrameworkDIR64 3632 | FrameworkVersion64 3633 | VCToolsRedistDir 3634 | VCINSTALLDIR 3635 | VCToolsInstallDir 3636 | VCToolsVersion 3637 | WindowsLibPath 3638 | WindowsSdkDir 3639 | WindowsSDKVersion 3640 | WindowsSdkBinPath 3641 | WindowsSdkVerBinPath 3642 | WindowsSDKLibVersion 3643 | __DOTNET_ADD_32BIT 3644 | __DOTNET_ADD_64BIT 3645 | __DOTNET_PREFERRED_BITNESS 3646 | Framework{framework version}Version 3647 | NETFXSDKDir 3648 | UniversalCRTSdkDir 3649 | UCRTVersion 3650 | ExtensionSdkDir 3651 | 3652 | These last 2 are set to ensure that distuils uses these environment 3653 | variables when compiling libopenzwave.pyd 3654 | MSSDK 3655 | DISTUTILS_USE_SDK 3656 | 3657 | :return: dict of environment variables 3658 | """ 3659 | path = os.environ.get('Path', '') 3660 | 3661 | env = dict( 3662 | __VSCMD_PREINIT_PATH=path, 3663 | Platform=self.platform, 3664 | VSCMD_ARG_app_plat='Desktop', 3665 | VSCMD_ARG_HOST_ARCH=self.platform, 3666 | VSCMD_ARG_TGT_ARCH=self.platform, 3667 | __VSCMD_script_err_count='0' 3668 | ) 3669 | 3670 | env_path = set() 3671 | 3672 | def update_env(cls): 3673 | for key, value in cls: 3674 | if key == 'Path': 3675 | for item in value.split(os.pathsep): 3676 | env_path.add(item) 3677 | continue 3678 | 3679 | if key in env: 3680 | env[key] += os.pathsep + value 3681 | else: 3682 | env[key] = value 3683 | 3684 | update_env(self.visual_c) 3685 | update_env(self.visual_studio) 3686 | update_env(self.windows_sdk) 3687 | update_env(self.dot_net) 3688 | 3689 | env_path = os.pathsep.join(item for item in env_path) 3690 | env['Path'] = env_path 3691 | 3692 | return env 3693 | 3694 | def __str__(self): 3695 | template = ( 3696 | 'Machine architecture: {machine_architecture}\n' 3697 | 'Build architecture: {architecture}\n' 3698 | ) 3699 | 3700 | res = [ 3701 | template.format( 3702 | machine_architecture=self.machine_architecture, 3703 | architecture=self.platform 3704 | ), 3705 | str(self.python), 3706 | str(self.visual_studio), 3707 | str(self.visual_c), 3708 | str(self.windows_sdk), 3709 | str(self.dot_net), 3710 | ] 3711 | 3712 | return '\n'.join(res) 3713 | 3714 | 3715 | def setup_environment( 3716 | minimum_c_version: Optional[Union[int, float]] = None, 3717 | strict_c_version: Optional[Union[int, float]] = None, 3718 | minimum_toolkit_version: Optional[int] = None, 3719 | strict_toolkit_version: Optional[int] = None, 3720 | minimum_sdk_version: Optional[str] = None, 3721 | strict_sdk_version: Optional[str] = None, 3722 | minimum_net_version: Optional[str] = None, 3723 | strict_net_version: Optional[str] = None, 3724 | vs_version: Optional[Union[str, int]] = None 3725 | ): 3726 | """ 3727 | Main entry point. 3728 | 3729 | There was some forwward thinking done when distutils was written. We are 3730 | able to set up a build environment without ever having to monkey patch any 3731 | part of distutils. There is an environment variable that I can set that will 3732 | tell distutils to use the existing environment for the build. This is 3733 | how I am able to set up a build environent and then let distutils do what 3734 | it does. The only portion of distutils that does not seem to function 3735 | properly 100% of the time is the MSVC detection. That is what gets fixed 3736 | with this library. 3737 | 3738 | :param minimum_c_version: The lowest MSVC compiler version to allow. 3739 | :type minimum_c_version: optional - int, float 3740 | 3741 | :param strict_c_version: The MSVC compiler version that MUST be used. 3742 | ie 14.0, 14.2 3743 | :type strict_c_version: optional - int or float 3744 | 3745 | :param minimum_toolkit_version: The lowest build tools version to allow. 3746 | :type minimum_toolkit_version: optional - int 3747 | 3748 | :param strict_toolkit_version: The build tools version that MUST be used. 3749 | ie 142, 143 3750 | :type strict_toolkit_version: optional - int 3751 | 3752 | :param minimum_sdk_version: The lowest SDK version to allow. 3753 | This can work several ways, if you want to specify a specific version as 3754 | the minimum `"10.0.22000.0"` or if you want to make sure that only 3755 | Windows 10 SDK's get used `"10.0"` 3756 | :type minimum_sdk_version: optional - str 3757 | 3758 | :param strict_sdk_version: The Windows SDK that MUST be used. 3759 | Whole version only. 3760 | :type strict_sdk_version: optional - str 3761 | 3762 | :param minimum_net_version: Works the same as minimum_sdk_version 3763 | :type minimum_net_version: optional - str 3764 | 3765 | :param strict_net_version: works the same as strict_sdk_version 3766 | :type strict_net_version: optional - str 3767 | 3768 | :param vs_version: The version of visual studio you want to use. 3769 | This can be one of the following version types. 3770 | 3771 | * version: `str("16.10.31515.178")` 3772 | * display version:`str("16.10.4")` 3773 | * product line version: `int(2019)`. 3774 | 3775 | If you have 2 installations that share the same product line version 3776 | the installation with the higher version will get used. An example of 3777 | this is Visual Studio 20019 and Build Tools 2019. If you want to specify 3778 | a specific installation in this case then use the version or 3779 | display version options. 3780 | :type vs_version: optional - str, int 3781 | 3782 | :return: Environment instance 3783 | :rtype: Environment 3784 | """ 3785 | 3786 | if not _IS_WIN: 3787 | raise RuntimeError( 3788 | 'This script will only work with a Windows opperating system.' 3789 | ) 3790 | 3791 | logger.debug( 3792 | 'Setting up Windows build environment, please wait.....' 3793 | ) 3794 | 3795 | environment = Environment( 3796 | minimum_c_version, 3797 | strict_c_version, 3798 | minimum_toolkit_version, 3799 | strict_toolkit_version, 3800 | minimum_sdk_version, 3801 | strict_sdk_version, 3802 | minimum_net_version, 3803 | strict_net_version, 3804 | vs_version 3805 | ) 3806 | 3807 | logger.debug('\n' + str(environment) + '\n\n') 3808 | logger.debug('SET ENVIRONMENT VARIABLES') 3809 | logger.debug('------------------------------------------------') 3810 | logger.debug('\n') 3811 | 3812 | for key, value in environment.build_environment.items(): 3813 | old_val = os.environ.get(key, value) 3814 | if old_val != value: 3815 | if os.pathsep in old_val or os.pathsep in value: 3816 | value = [item for item in set(value.split(os.pathsep))] 3817 | old_val = [ 3818 | item for item in set(old_val.split(os.pathsep)) 3819 | if item not in value 3820 | ] 3821 | 3822 | value.extend(old_val) 3823 | value = os.pathsep.join( 3824 | item.strip() for item in value if item.strip() 3825 | ) 3826 | 3827 | logger.debug(key + '=' + value) 3828 | os.environ[key] = value 3829 | 3830 | logger.debug('\n\n') 3831 | 3832 | return environment 3833 | 3834 | 3835 | if __name__ == '__main__': 3836 | _setup_logging() 3837 | 3838 | # build tools 2019 '16.10.31515.178' '16.10.4' 3839 | # visual studio 2019 '16.11.31729.503' '16.11.5' 3840 | 3841 | envr = setup_environment() # vs_version='16.10.4') 3842 | --------------------------------------------------------------------------------