From 9567b29c439b96f4533dd06f441e2124a600bb32 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 17 Jan 2022 05:47:33 -0700 Subject: [PATCH 01/23] Fixes MSVC build environment --- .gitignore | 5 + build.py | 95 +- buildtools/msvc/__init__.py | 3632 +++++++++++++++++++++++++++++++++++ buildtools/msvc/vswhere.py | 1506 +++++++++++++++ 4 files changed, 5208 insertions(+), 30 deletions(-) create mode 100644 buildtools/msvc/__init__.py create mode 100644 buildtools/msvc/vswhere.py diff --git a/.gitignore b/.gitignore index d84a059a8e..ba5c892dce 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,11 @@ wingdbstub.py* mydbstub.py* +# temporary storage location for modules +# that are needed when setting up a build +# environment in Windows +.eggs + .idea .cache .vagrant diff --git a/build.py b/build.py index 7269d95919..1d21c8587b 100755 --- a/build.py +++ b/build.py @@ -785,37 +785,72 @@ def uploadTree(srcPath, destPath, options, days=30): def checkCompiler(quiet=False): if isWindows: - # Make sure that the compiler that Python wants to use can be found. - # It will terminate if the compiler is not found or other exceptions - # are raised. - cmd = "import setuptools, distutils.msvc9compiler as msvc; " \ - "mc = msvc.MSVCCompiler(); " \ - "mc.initialize(); " \ - "print(mc.cc)" - CC = runcmd('"%s" -c "%s"' % (PYTHON, cmd), getOutput=True, echoCmd=False) + import setuptools + + # we use the internal mechanics of setup tools to collect comtypes + # which is a library used to build the COM interfaces to Visual Studio + # In order for those mechanics to work properly the wheel library needs + # to be available in the python installation and it cannot be added + # during runtime. This is because of how setuptools turns the collected + # library into an egg. + try: + __import__('wheel') + except ImportError: + raise RuntimeError( + 'The "wheel" library is required to build wxPython for Windows' + ) + + setuptools.setup( + name='msvc_compiler', + script_args=['build'], + setup_requires=['comtypes'], + # dependency_links=[ + # 'https://github.com/enthought/comtypes' + # '/tarball/1.1.10.tar.gz#egg=comtypes' + # ] + ) + + from buildtools import msvc + + # setup the build environment making the minimum conpiler version 14.2 + # A user does not need to have a full blown Visual Studio installation + # in order to build wxPython. They can use Visual Studio Build Tools + # as well. Build Tools doe not include any of the GUI replated + # components of Visual Studio. + # If the msvc script isunable to locate an MSVC compiler that is of + # version >= 14.2 the msvc script will raise RuntimeError. + + # the msvc script works in a manner where no part of either setuptools + # or distutils is overriden. It uses mechanics that can alter what + # setuptools and distutils sees and uses for the build environment + # without tampering with any of the original code in those libraries. + + # the msvc script does not use the problematic vcvars*.bat files that + # come with an MSVC compiler. It builds the environment from the ground + # up by reading the registry and using the COM interfaces. The + # subprocess module is only used for Visual Studio installatons that + # are older then 2017 which wouldn't be an issue because the wxPython + # build system needs the MSVC compiler to be at least 14.2 (VS 2019) + + # If the environment variable "DISTUTILS_DEBUG=1" is set There is + # going to be a HUGE dump of information from the msvc module to + # stdout by way of distutils.log.debug(). If the logging level is set + # to DEBUG by using distutils.log.set_threshold(distutils.log.DEBUG) + # or any other mechanics to set the logging level to DEBUG here will + # be a heap of information dumped to stdout by the msvc module. + + # Just so this is on record and can be used at a later date if needs + # be the MSVC compiler defaults to c++11. If you need to change this + # extra compiler arguments will have to be added, "/std:cpp_version" + # where cpp_version is the version of cpp to be used ie: "c++20". + # The __cplusplus macro does not change when using the /std compiler + # switch. You have to specifically tell the compuler to update the + # value for the __cplusplus macro using "/Zc:__cplusplus" + environment = msvc.setup_environment(minimum_c_version=14.2) + if not quiet: - msg("MSVC: %s" % CC) - - # Now get the environment variables which that compiler needs from - # its vcvarsall.bat command and load them into this process's - # environment. - cmd = "import setuptools, distutils.msvc9compiler as msvc; " \ - "arch = msvc.PLAT_TO_VCVARS[msvc.get_platform()]; " \ - "env = msvc.query_vcvarsall(msvc.VERSION, arch); " \ - "print(env)" - env = eval(runcmd('"%s" -c "%s"' % (PYTHON, cmd), getOutput=True, echoCmd=False)) - - def _b(v): - return str(v) - #if PY2: - # return bytes(v) - #else: - # return bytes(v, 'utf8') - - os.environ['PATH'] = _b(env['path']) - os.environ['INCLUDE'] = _b(env['include']) - os.environ['LIB'] = _b(env['lib']) - os.environ['LIBPATH'] = _b(env['libpath']) + # this gives a simple output of the build environment. + print(environment) # NOTE: SIP is now generating code with scoped-enums. Older linux # platforms like what we're using for builds, and also TravisCI for diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py new file mode 100644 index 0000000000..dc4a99f7ef --- /dev/null +++ b/buildtools/msvc/__init__.py @@ -0,0 +1,3632 @@ +# -*- coding: utf-8 -*- +# +# ############################################################################# +# +# MIT License +# +# Copyright 2022 Kevin G. Schlosser +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# +# ############################################################################# + +# This tool is used to create an identical build environment to what is created +# when building a Visual Studio project or using any of the vcvars and vsvars +# batch files. There is a similar tool included with SetupTools and it is +# called msvc. The setup tools version is not complete and it is also error +# prone. It does not make an identical build environment. + +import os +import sys +import ctypes +import subprocess +import winreg +import distutils.log +from typing import Optional, Union + + +_IS_WIN = sys.platform.startswith('win') + + +if _IS_WIN: + try: + from . import vswhere + except ImportError: + import vswhere + + +_HRESULT = ctypes.c_long +_BOOL = ctypes.c_bool +_DWORD = ctypes.c_ulong +_LPCVOID = ctypes.c_void_p +_LPCWSTR = ctypes.c_wchar_p +_LPVOID = ctypes.c_void_p +_UINT = ctypes.c_uint +_INT = ctypes.c_int +_HANDLE = ctypes.c_void_p +_HWND = ctypes.c_void_p +_LPWSTR = ctypes.c_wchar_p +_POINTER = ctypes.POINTER +_CHAR = _INT +_PUINT = _POINTER(_UINT) +_LPDWORD = _POINTER(_DWORD) + + +if _IS_WIN: + try: + _vswhere = vswhere.SetupConfiguration.GetSetupConfiguration() + except: # NOQA + _vswhere = None +else: + _vswhere = None + + +# noinspection PyPep8Naming +class _VS_FIXEDFILEINFO(ctypes.Structure): + _fields_ = [ + ("dwSignature", _DWORD), # will be 0xFEEF04BD + ("dwStrucVersion", _DWORD), + ("dwFileVersionMS", _DWORD), + ("dwFileVersionLS", _DWORD), + ("dwProductVersionMS", _DWORD), + ("dwProductVersionLS", _DWORD), + ("dwFileFlagsMask", _DWORD), + ("dwFileFlags", _DWORD), + ("dwFileOS", _DWORD), + ("dwFileType", _DWORD), + ("dwFileSubtype", _DWORD), + ("dwFileDateMS", _DWORD), + ("dwFileDateLS", _DWORD) + ] + + +if _IS_WIN: + _version = ctypes.windll.version + + _GetFileVersionInfoSize = _version.GetFileVersionInfoSizeW + _GetFileVersionInfoSize.restype = _DWORD + _GetFileVersionInfoSize.argtypes = [_LPCWSTR, _LPDWORD] + + _VerQueryValue = _version.VerQueryValueW + _VerQueryValue.restype = _BOOL + _VerQueryValue.argtypes = [_LPCVOID, _LPCWSTR, _POINTER(_LPVOID), _PUINT] + + _GetFileVersionInfo = _version.GetFileVersionInfoW + _GetFileVersionInfo.restype = _BOOL + _GetFileVersionInfo.argtypes = [_LPCWSTR, _DWORD, _DWORD, _LPVOID] + + + def _get_file_version(filename): + dw_len = _GetFileVersionInfoSize(filename, None) + if not dw_len: + raise ctypes.WinError() + + lp_data = (_CHAR * dw_len)() + if not _GetFileVersionInfo( + filename, + 0, + ctypes.sizeof(lp_data), lp_data + ): + raise ctypes.WinError() + + u_len = _UINT() + lpffi = _POINTER(_VS_FIXEDFILEINFO)() + lplp_buffer = ctypes.cast(ctypes.pointer(lpffi), _POINTER(_LPVOID)) + if not _VerQueryValue(lp_data, "\\", lplp_buffer, ctypes.byref(u_len)): + raise ctypes.WinError() + + ffi = lpffi.contents + return ( + ffi.dwFileVersionMS >> 16, + ffi.dwFileVersionMS & 0xFFFF, + ffi.dwFileVersionLS >> 16, + ffi.dwFileVersionLS & 0xFFFF, + ) + + + _CSIDL_PROGRAM_FILES = 0x26 + _CSIDL_PROGRAM_FILESX86 = 0x2A + + _SHGFP_TYPE_CURRENT = 0 + _MAX_PATH = 260 + _CSIDL_FLAG_DONT_VERIFY = 16384 + + _shell32 = ctypes.windll.Shell32 + + # noinspection PyUnboundLocalVariable + _SHGetFolderPathW = _shell32.SHGetFolderPathW + _SHGetFolderPathW.restype = _HRESULT + _SHGetFolderPathW.argtypes = [_HWND, _INT, _HANDLE, _DWORD, _LPWSTR] + + _buf = ctypes.create_unicode_buffer(_MAX_PATH) + + _SHGetFolderPathW( + 0, + _CSIDL_PROGRAM_FILESX86 | _CSIDL_FLAG_DONT_VERIFY, + 0, + _SHGFP_TYPE_CURRENT, + _buf + ) + + _PROGRAM_FILES_X86 = _buf.value + + _buf = ctypes.create_unicode_buffer(_MAX_PATH) + + _SHGetFolderPathW( + 0, + _CSIDL_PROGRAM_FILES | _CSIDL_FLAG_DONT_VERIFY, + 0, + _SHGFP_TYPE_CURRENT, + _buf + ) + + _PROGRAM_FILES = _buf.value + + del _buf + del _SHGetFolderPathW + del _shell32 + del _CSIDL_PROGRAM_FILES + del _CSIDL_PROGRAM_FILESX86 + del _SHGFP_TYPE_CURRENT + del _MAX_PATH + del _CSIDL_FLAG_DONT_VERIFY + +else: + def _get_file_version(_): + pass + + _PROGRAM_FILES_X86 = '' + _PROGRAM_FILES = '' + + +_found_cl = {} + + +def _find_cl(path): + if path in _found_cl: + return _found_cl[path] + + for root, dirs, files in os.walk(path): + if 'cl.exe' not in files: + continue + + if 'MSVC' in root: + head, tail = os.path.split(root) + + while head and not head.endswith('MSVC'): + head, tail = os.path.split(head) + + if head: + _found_cl[path] = [[head.split('\\VC\\')[0] + '\\VC', tail]] + else: + _found_cl[path] = [] + else: + root = root.split('\\VC\\')[0] + ver = os.path.split(root)[1] + + _found_cl[path] = [[root + '\\VC', ver.split(' ')[-1]]] + + return _found_cl[path] + + _found_cl[path] = [] + + return [] + + +def _get_program_files_vc(): + pths = [ + os.path.join(_PROGRAM_FILES_X86, f) + for f in os.listdir(_PROGRAM_FILES_X86) + if 'Visual Studio' in f + ] + res = [ + item for pth in pths for item in _find_cl(pth) + ] + + return res + + +def _get_reg_value(path, key, wow6432=False): + d = _read_reg_values(path, wow6432) + if key in d: + return d[key] + + return '' + + +def _read_reg_keys(key, wow6432=False): + if isinstance(key, tuple): + root = key[0] + key = key[1] + else: + root = winreg.HKEY_LOCAL_MACHINE + key = 'SOFTWARE\\Microsoft\\' + key + + try: + if wow6432: + handle = winreg.OpenKey( + root, + key, + 0, + winreg.KEY_READ | winreg.KEY_WOW64_32KEY + ) + else: + handle = winreg.OpenKeyEx(root, key) + except winreg.error: + return [] + res = [] + + for i in range(winreg.QueryInfoKey(handle)[0]): + res += [winreg.EnumKey(handle, i)] + + winreg.CloseKey(handle) + return res + + +def _read_reg_values(key, wow6432=False): + if isinstance(key, tuple): + root = key[0] + key = key[1] + else: + root = winreg.HKEY_LOCAL_MACHINE + key = 'SOFTWARE\\Microsoft\\' + key + + try: + if wow6432: + handle = winreg.OpenKey( + root, + key, + 0, + winreg.KEY_READ | winreg.KEY_WOW64_32KEY + ) + else: + handle = winreg.OpenKeyEx(root, key) + except winreg.error: + return {} + res = {} + for i in range(winreg.QueryInfoKey(handle)[1]): + name, value, _ = winreg.EnumValue(handle, i) + res[_convert_mbcs(name)] = _convert_mbcs(value) + + winreg.CloseKey(handle) + + return res + + +def _convert_mbcs(s): + dec = getattr(s, "decode", None) + if dec is not None: + try: + s = dec("mbcs") + except UnicodeError: + pass + return s + + +def _convert_version(ver): + if isinstance(ver, str): + ver = tuple(int(item) for item in ver.split('.')) + elif isinstance(ver, bytes): + ver = tuple( + int(item) for item in ver.decode('utf-8').split('.') + ) + elif isinstance(ver, int): + ver = (ver,) + elif isinstance(ver, float): + ver = tuple( + int(item) for item in str(ver).split('.') + ) + elif isinstance(ver, list): + ver = tuple(int(item) for item in ver) + + if not isinstance(ver, tuple): + raise TypeError( + 'Version is not correct type({0})'.format(type(ver)) + ) + + ver = '.'.join(str(item) for item in ver) + + return ver + + +class Environment(object): + + def __init__( + self, + minimum_c_version: Optional[Union[int, float]] = None, + strict_c_version: Optional[Union[int, float]] = None, + minimum_toolkit_version: Optional[int] = None, + strict_toolkit_version: Optional[int] = None, + minimum_sdk_version: Optional[str] = None, + strict_sdk_version: Optional[str] = None, + minimum_net_version: Optional[str] = None, + strict_net_version: Optional[str] = None, + vs_version: Optional[Union[str, int]] = None + ): + self.python = PythonInfo() + + self.visual_c = VisualCInfo( + self, + minimum_c_version, + strict_c_version, + minimum_toolkit_version, + strict_toolkit_version, + vs_version + ) + + self.visual_studio = VisualStudioInfo( + self, + self.visual_c + ) + + self.windows_sdk = WindowsSDKInfo( + self, + self.visual_c, + minimum_sdk_version, + strict_sdk_version + ) + + self.dot_net = NETInfo( + self, + self.visual_c, + self.windows_sdk.version, + minimum_net_version, + strict_net_version + ) + + @property + def machine_architecture(self): + import platform + return 'x64' if '64' in platform.machine() else 'x86' + + @property + def platform(self): + """ + :return: x86 or x64 + """ + import platform + + win_64 = self.machine_architecture == 'x64' + python_64 = platform.architecture()[0] == '64bit' and win_64 + + return 'x64' if python_64 else 'x86' + + @property + def configuration(self): + """ + Build configuration + :return: one of ReleaseDLL, DebugDLL + """ + + if os.path.splitext(sys.executable)[0].endswith('_d'): + config = 'Debug' + else: + config = 'Release' + + return config + + def __iter__(self): + for item in self.build_environment.items(): + yield item + + @property + def build_environment(self): + """ + This would be the work horse. This is where all of the gathered + information is put into a single container and returned. + The information is then added to os.environ in order to allow the + build process to run properly. + + List of environment variables generated: + PATH + LIBPATH + LIB + INCLUDE + Platform + FrameworkDir + FrameworkVersion + FrameworkDIR32 + FrameworkVersion32 + FrameworkDIR64 + FrameworkVersion64 + VCToolsRedistDir + VCINSTALLDIR + VCToolsInstallDir + VCToolsVersion + WindowsLibPath + WindowsSdkDir + WindowsSDKVersion + WindowsSdkBinPath + WindowsSdkVerBinPath + WindowsSDKLibVersion + __DOTNET_ADD_32BIT + __DOTNET_ADD_64BIT + __DOTNET_PREFERRED_BITNESS + Framework{framework version}Version + NETFXSDKDir + UniversalCRTSdkDir + UCRTVersion + ExtensionSdkDir + + These last 2 are set to ensure that distuils uses these environment + variables when compiling libopenzwave.pyd + MSSDK + DISTUTILS_USE_SDK + + :return: dict of environment variables + """ + path = os.environ.get('Path', '') + + env = dict( + __VSCMD_PREINIT_PATH=path, + Platform=self.platform, + VSCMD_ARG_app_plat='Desktop', + VSCMD_ARG_HOST_ARCH=self.platform, + VSCMD_ARG_TGT_ARCH=self.platform, + __VSCMD_script_err_count='0' + ) + + env_path = set() + + def update_env(cls): + for key, value in cls: + if key == 'Path': + for item in value.split(';'): + env_path.add(item) + continue + + if key in env: + env[key] += ';' + value + else: + env[key] = value + + update_env(self.visual_c) + update_env(self.visual_studio) + update_env(self.windows_sdk) + update_env(self.dot_net) + + env['Path'] = (';'.join(env_path)) + ';' + path + + return env + + def __str__(self): + template = ( + 'Machine architecture: {machine_architecture}\n' + 'Build architecture: {architecture}\n' + ) + + res = [ + template.format( + machine_architecture=self.machine_architecture, + architecture=self.platform + ), + str(self.python), + str(self.visual_studio), + str(self.visual_c), + str(self.windows_sdk), + str(self.dot_net), + ] + + return '\n'.join(res) + + +# I have separated the environment into several classes +# Environment - the main environment class. +# the environment class is what is going to get used. this handles all of the +# non specific bits of the environment. all of the rest of the classes are +# brought together in the environment to form a complete build environment. + +# NETInfo - Any .NET related environment settings + +# WindowsSDKInfo - Any Windows SDK environment settings + +# VisualStudioInfo - Any VisualStudios environment settings (if applicable) + +# VisualCInfo - Any VisualC environment settings + +# PythonInfo - This class really isnt for environment settings as such. +# It is more of a convenience class. it will get things like a list of the +# includes specific to the python build. the architecture of the version of +# python that is running stuff along those lines. +class PythonInfo(object): + + @property + def architecture(self): + return 'x64' if sys.maxsize > 2 ** 32 else 'x86' + + @property + def version(self): + return '.'.join(str(ver) for ver in sys.version_info) + + @property + def dependency(self): + return 'Python%d%d.lib' % sys.version_info[:2] + + @property + def includes(self): + python_path = os.path.dirname(sys.executable) + python_include = os.path.join(python_path, 'include') + + python_includes = [python_include] + for root, dirs, files in os.walk(python_include): + for d in dirs: + python_includes += [os.path.join(root, d)] + return python_includes + + @property + def libraries(self): + python_path = os.path.dirname(sys.executable) + python_lib = os.path.join(python_path, 'libs') + + python_libs = [python_lib] + for root, dirs, files in os.walk(python_lib): + for d in dirs: + python_libs += [os.path.join(root, d)] + return python_libs + + def __str__(self): + template = ( + '== Python =====================================================\n' + ' version: {py_version}\n' + ' architecture: {py_architecture}\n' + ' library: {py_dependency}\n' + ' libs: {py_libraries}\n' + ' includes: {py_includes}\n' + ) + return template.format( + py_version=self.version, + py_architecture=self.architecture, + py_dependency=self.dependency, + py_libraries=self.libraries, + py_includes=self.includes + ) + + +class VisualCInfo(object): + + def __init__( + self, + environ: Environment, + minimum_c_version: Optional[Union[int, float]] = None, + strict_c_version: Optional[Union[int, float]] = None, + minimum_toolkit_version: Optional[int] = None, + strict_toolkit_version: Optional[int] = None, + vs_version: Optional[Union[str, int]] = None + ): + self.environment = environ + self.platform = environ.platform + self.strict_c_version = strict_c_version + self.__installed_versions = None + self._cpp_installation = None + self._ide_install_directory = None + self._install_directory = None + self._cpp_version = None + self._tools_version = None + self._toolset_version = None + self._msvc_dll_path = None + self._tools_redist_directory = None + self._tools_install_directory = None + self._msbuild_version = None + self._msbuild_path = None + self._product_semantic_version = None + self._devinit_path = None + + self._strict_toolkit_version = strict_toolkit_version + self._minimum_toolkit_version = minimum_toolkit_version + py_version = sys.version_info[:2] + if py_version in ((3, 4),): + min_visual_c_version = 10.0 + elif py_version in ((3, 5), (3, 6), (3, 7), (3, 8)): + min_visual_c_version = 14.0 + elif py_version in ((3, 9), (3, 10)): + min_visual_c_version = 14.2 + else: + raise RuntimeError( + 'This library does not support ' + 'python version %d.%d' % py_version + ) + + if ( + strict_c_version is not None and + strict_c_version < min_visual_c_version + ): + raise RuntimeError( + 'The set minimum compiler version is lower then the ' + 'required compiler version for Python' + ) + + if minimum_c_version is None: + minimum_c_version = min_visual_c_version + + if strict_toolkit_version is not None: + strict_toolkit_version = str(strict_toolkit_version / 10.0) + + if minimum_toolkit_version is not None: + minimum_toolkit_version = str(minimum_toolkit_version / 10.0) + + self.minimum_c_version = minimum_c_version + self._strict_toolkit_version = strict_toolkit_version + self._minimum_toolkit_version = minimum_toolkit_version + + if _vswhere is not None: + cpp_id = 'Microsoft.VisualCpp.Tools.Host{0}.Target{1}'.format( + environ.machine_architecture.upper(), + environ.python.architecture.upper() + ) + + tools_id = ( + 'Microsoft.VisualCpp.Premium.Tools.' + 'Host{0}.Target{1}' + ).format( + environ.machine_architecture.upper(), + environ.python.architecture.upper() + ) + + cpp_version = None + cpp_installation = None + + for installation in _vswhere: + + distutils.log.debug(str(installation) + '\n\n') + + if vs_version is not None: + try: + if isinstance(vs_version, str): + display_version = str( + installation.catalog.product_display_version + ) + + if ( + installation.version != vs_version and + display_version != vs_version + ): + continue + else: + product_line_version = int( + installation.catalog.product_line_version + ) + + if product_line_version != vs_version: + continue + + except: # NOQA + continue + + for package in installation.packages.vsix: + if package.id == cpp_id: + if ( + self.strict_c_version is not None and + package != self.strict_c_version + ): + continue + + if ( + self.minimum_c_version is not None and + package < self.minimum_c_version + ): + continue + + if cpp_version is None: + cpp_version = package + cpp_installation = installation + elif package > cpp_version: + cpp_version = package + cpp_installation = installation + + if cpp_installation is not None: + self._cpp_version = cpp_version.version + self._cpp_installation = cpp_installation + + tools_version = None + tools_path = os.path.join( + cpp_installation.path, + 'VC', + 'Tools', + 'MSVC' + ) + + for package in cpp_installation.packages.vsix: + if package.id == tools_id: + if not os.path.exists( + os.path.join(tools_path, package.version) + ): + continue + + if strict_toolkit_version is not None: + if package == strict_toolkit_version: + tools_version = package + break + + if minimum_toolkit_version is not None: + if package <= minimum_toolkit_version: + continue + + if tools_version is None: + tools_version = package + elif tools_version > package: + tools_version = package + + if tools_version is None: + for file in os.listdir(tools_path): + if strict_toolkit_version is not None: + tk_version = file[:len(strict_toolkit_version)] + if tk_version == strict_toolkit_version: + tools_version = file + break + + if minimum_toolkit_version is not None: + tk_version = file[:len(minimum_toolkit_version)] + if tk_version < minimum_toolkit_version: + continue + + if tools_version is None: + tools_version = file + elif tools_version < file: + tools_version = file + + else: + tools_version = tools_version.version + + if tools_version is not None: + tools_version = tools_version.split('.') + self._toolset_version = ( + 'v' + tools_version[0] + + tools_version[1][0] + ) + self._tools_version = '.'.join(tools_version) + + self._tools_install_directory = os.path.join( + tools_path, self._tools_version + ) + + tools_redist_directory = ( + self._tools_install_directory.replace( + 'Tools', + 'Redist' + ) + ) + + if os.path.exists(tools_redist_directory): + self._tools_redist_directory = ( + tools_redist_directory + '\\' + ) + + msvc_dll_path = os.path.join( + tools_redist_directory, + environ.python.architecture, + 'Microsoft.VC{0}.CRT'.format( + self._toolset_version[1:] + ) + ) + + if os.path.exists(msvc_dll_path): + self._msvc_dll_path = msvc_dll_path + + install_directory = os.path.join( + cpp_installation.path, 'VC' + ) + if os.path.exists(install_directory): + self._install_directory = install_directory + + msbuild_path = os.path.join( + cpp_installation.path, + 'MSBuild', + 'Current', + 'Bin', + 'MSBuild.exe' + ) + + if os.path.exists(msbuild_path): + msbuild_version = _get_file_version(msbuild_path) + self._msbuild_version = '.'.join( + str(item) for item in msbuild_version + ) + self._msbuild_path = msbuild_path + + ide_directory = os.path.join( + cpp_installation.path, + 'Common7', + 'IDE', + 'VC' + ) + if os.path.exists(ide_directory): + self._ide_install_directory = ide_directory + + product_semantic_version = ( + cpp_installation.catalog.product_semantic_version + ) + if product_semantic_version is not None: + self._product_semantic_version = ( + product_semantic_version.split('+')[0] + ) + + devinit_path = os.path.join( + cpp_installation.path, + 'Common7', + 'Tools', + 'devinit', + 'devinit.exe' + ) + if os.path.exists(devinit_path): + self._devinit_path = devinit_path + + @property + def cmake_paths(self) -> list: + paths = [] + ide_path = os.path.split(self.ide_install_directory)[0] + + cmake_path = os.path.join( + ide_path, + 'CommonExtensions', + 'Microsoft', + 'CMake' + ) + + if os.path.exists(cmake_path): + bin_path = os.path.join(cmake_path, 'CMake', 'bin') + ninja_path = os.path.join(cmake_path, 'Ninja') + if os.path.exists(bin_path): + paths += [bin_path] + + if os.path.exists(ninja_path): + paths += [ninja_path] + + return paths + + @property + def cpp_installation( + self + ) -> Union[vswhere.ISetupInstance, vswhere.ISetupInstance2]: + return self._cpp_installation + + @property + def f_sharp_path(self) -> Optional[str]: + version = float(int(self.version.split('.')[0])) + + reg_path = ( + winreg.HKEY_LOCAL_MACHINE, + r'SOFTWARE\Microsoft\VisualStudio' + r'\{0:.1f}\Setup\F#'.format(version) + ) + + f_sharp_path = _get_reg_value(reg_path, 'ProductDir') + if f_sharp_path and os.path.exists(f_sharp_path): + return f_sharp_path + + path = r'C:\Program Files (x86)\Microsoft SDKs\F#' + if os.path.exists(path): + versions = os.listdir(path) + max_ver = 0.0 + found_version = '' + + for version in versions: + try: + ver = float(version) + except ValueError: + continue + + if ver > max_ver: + max_ver = ver + found_version = version + + f_sharp_path = os.path.join( + path, + found_version, + 'Framework', + 'v' + found_version + ) + + if os.path.exists(f_sharp_path): + return f_sharp_path + + install_dir = os.path.split(self.install_directory)[0] + f_sharp_path = os.path.join( + install_dir, + 'Common7', + 'IDE', + 'CommonExtensions', + 'Microsoft', + 'FSharp', + 'Tools' + ) + if os.path.exists(f_sharp_path): + return f_sharp_path + + @property + def ide_install_directory(self) -> str: + if self._ide_install_directory is None: + directory = self.install_directory + ide_directory = os.path.abspath( + os.path.join(directory, '..') + ) + + ide_directory = os.path.join( + ide_directory, + 'Common7', + 'IDE', + 'VC' + ) + if os.path.exists(ide_directory): + self._ide_install_directory = ide_directory + + return self._ide_install_directory + + @property + def install_directory(self) -> str: + """ + Visual C path + :return: Visual C path + """ + if self._install_directory is None: + self._install_directory = ( + self._installed_c_paths[self.version]['base'] + ) + + return self._install_directory + + @property + def _installed_c_paths(self): + if self.__installed_versions is None: + self.__installed_versions = {} + + def add(vers): + for base_pth, ver in vers: + if os.path.exists(base_pth): + base_ver = float(int(ver.split('.')[0])) + + self.__installed_versions[ver] = dict( + base=base_pth, + root=base_pth + ) + self.__installed_versions[base_ver] = dict( + base=base_pth, + root=base_pth + ) + + add(_get_program_files_vc()) + + reg_path = ( + winreg.HKEY_LOCAL_MACHINE, + r'SOFTWARE\Policies\Microsoft' + r'\VisualStudio\Setup' + ) + + vs_path = _get_reg_value(reg_path, 'SharedInstallationPath') + + if vs_path: + vs_path = os.path.split(vs_path)[0] + if os.path.exists(vs_path): + res = [] + + pths = [ + os.path.join(vs_path, vs_ver) + for vs_ver in os.listdir(vs_path) + if vs_ver.isdigit() + ] + res.extend( + [item for pth in pths for item in _find_cl(pth)] + ) + + add(res) + + reg_path = ( + winreg.HKEY_CLASSES_ROOT, + r'Local Settings\Software' + r'\Microsoft\Windows\Shell\MuiCache' + ) + + paths = [] + + for key in _read_reg_values(reg_path): + if 'cl.exe' in key: + value = _get_reg_value(reg_path, key) + if 'C++ Compiler Driver' in value: + paths += [key] + + elif 'devenv.exe' in key: + if not os.path.exists(key): + continue + + value = _get_reg_value(reg_path, key) + + if value.startswith('Microsoft Visual Studio '): + + head, tail = os.path.split(key) + while tail != 'Common7' and head: + head, tail = os.path.split(head) + + if head: + add(_find_cl(head)) + + for path in paths: + if not os.path.exists(path): + continue + + if '\\VC\\bin' in path: + version = path.split('\\VC\\bin')[0] + elif '\\bin\\Host' in path: + version = path.split('\\bin\\Host')[0] + else: + continue + + version = os.path.split(version)[1] + version = version.replace( + 'Microsoft Visual Studio', + '' + ).strip() + base_version = float(int(version.split('.')[0])) + + base_path = path.split('\\VC\\')[0] + '\\VC' + if os.path.exists(os.path.join(base_path, 'include')): + vc_root = base_path + else: + vc_root = path.split('\\bin\\')[0] + + self.__installed_versions[version] = dict( + base=base_path, + root=vc_root + ) + self.__installed_versions[base_version] = dict( + base=base_path, + root=vc_root + ) + + reg_path = ( + winreg.HKEY_LOCAL_MACHINE, + 'SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7' + ) + + for key in _read_reg_values(reg_path): + try: + version = float(key) + except ValueError: + continue + + path = _get_reg_value(reg_path, key) + + if ( + ( + os.path.exists(path) and + version not in self.__installed_versions + ) or version == 15.0 + ): + + if version == 15.0: + version = 14.0 + + if not os.path.split(path)[1] == 'VC': + path = os.path.join(path, 'VC') + + self.__installed_versions[version] = dict( + base=path, + root=path + ) + self.__installed_versions[key] = dict( + base=path, + root=path + ) + + return self.__installed_versions + + @property + def version(self) -> str: + """ + Visual C version + + Sometimes when building extension in python the version of the compiler + that was used to compile Python has to also be used to compile an + extension. I have this system set so it will automatically pick the + most recent compiler installation. this can be overridden in 2 ways. + The first way being that the compiler version that built Python has to + be used. The second way is you can set a minimum compiler version to + use. + + :return: found Visual C version + """ + if self._cpp_version is None: + if self.strict_c_version is not None: + if self.strict_c_version not in self._installed_c_paths: + raise RuntimeError( + 'No Compatible Visual C version found.' + ) + + self._cpp_version = str(self.strict_c_version) + return self._cpp_version + + match = None + for version in self._installed_c_paths: + if not isinstance(version, float): + continue + + if version >= self.minimum_c_version: + if match is None: + match = version + elif version < match: + match = version + + if match is None: + raise RuntimeError( + 'No Compatible Visual C\\C++ version found.' + ) + + self._cpp_version = str(match) + + return self._cpp_version + + @property + def tools_version(self) -> str: + if self._tools_version is None: + version = os.path.split(self.tools_install_directory)[1] + if not version.split('.')[-1].isdigit(): + version = str(self.version) + + self._tools_version = version + + return self._tools_version + + @property + def toolset_version(self) -> str: + """ + The platform toolset gets written to the solution file. this instructs + the compiler to use the matching MSVCPxxx.dll file. + """ + + if self._toolset_version is None: + tools_version = self.tools_version.split('.') + + self._toolset_version = ( + 'v' + + tools_version[0] + + tools_version[1][:1] + ) + # + # + # tool_path = self.tools_redist_directory + # x64 = self.platform == 'x64' + # + # dirs = os.listdir(self.tools_redist_directory) + # + # + # if x64: + # if 'amd64' in dirs: + # tool_path = os.path.join(tool_path, 'amd64') + # else: + # tool_path = os.path.join(tool_path, 'x64') + # + # else: + # tool_path = os.path.join(tool_path, 'x86') + # + # if os.path.exists(tool_path): + # max_ver = 0 + # + # for f in os.listdir(tool_path): + # if not f.endswith('CRT'): + # continue + # try: + # ver = int(f.split('.')[1].lstrip('VC')) + # max_ver = max(max_ver, ver) + # except: + # continue + # + # if max_ver != 0: + # self._toolset_version = 'v' + str(max_ver) + + return self._toolset_version + + @property + def msvc_dll_version(self) -> Optional[str]: + msvc_dll_path = self.msvc_dll_path + if not msvc_dll_path: + return + + for f in os.listdir(msvc_dll_path): + if f.endswith('dll'): + version = _get_file_version( + os.path.join(msvc_dll_path, f) + ) + return '.'.join(str(ver) for ver in version) + + @property + def msvc_dll_path(self) -> Optional[str]: + if self._msvc_dll_path is None: + x64 = self.platform == 'x64' + + toolset_version = self.toolset_version + + if toolset_version is None: + return None + + folder_names = ( + 'Microsoft.VC{0}.CRT'.format(toolset_version[1:]), + ) + + redist_path = self.tools_redist_directory + + for root, dirs, files in os.walk(redist_path): + def pass_directory(): + for item in ('onecore', 'arm', 'spectre'): + if item in root.lower(): + return True + return False + + if pass_directory(): + continue + + for folder_name in folder_names: + if folder_name in dirs: + if x64 and ('amd64' in root or 'x64' in root): + self._msvc_dll_path = os.path.join( + root, + folder_name + ) + break + elif ( + not x64 and + 'amd64' not in root + and 'x64' not in root + ): + self._msvc_dll_path = os.path.join( + root, + folder_name + ) + break + + return self._msvc_dll_path + + @property + def tools_redist_directory(self) -> Optional[str]: + if self._tools_redist_directory is None: + tools_install_path = self.tools_install_directory + + if 'MSVC' in tools_install_path: + redist_path = tools_install_path.replace( + 'Tools', + 'Redist' + ) + if ( + not os.path.exists(redist_path) and + 'BuildTools' in tools_install_path + ): + redist_path = redist_path.replace( + 'BuildRedist', 'BuildTools' + ) + else: + redist_path = os.path.join( + tools_install_path, + 'Redist' + ) + + if not os.path.exists(redist_path): + redist_path = os.path.split(redist_path)[0] + tools_version = None + + for version in os.listdir(redist_path): + if not os.path.isdir( + os.path.join(redist_path, version) + ): + continue + + if version.startswith('v'): + continue + + if self._strict_toolkit_version is not None: + tk_version = ( + version[:len(self._strict_toolkit_version)] + ) + if tk_version == self._strict_toolkit_version: + tools_version = version + break + + if self._minimum_toolkit_version is not None: + tk_version = ( + version[:len(self._minimum_toolkit_version)] + ) + if tk_version < self._minimum_toolkit_version: + continue + + if tools_version is None: + tools_version = version + elif tools_version < version: + tools_version = version + + if tools_version is not None: + self._tools_redist_directory = ( + os.path.join(redist_path, tools_version) + ) + else: + self._tools_redist_directory = '' + + else: + self._tools_redist_directory = redist_path + + if ( + self._tools_redist_directory and + not self._tools_redist_directory.endswith('\\') + ): + self._tools_redist_directory += '\\' + + return self._tools_redist_directory + + @property + def tools_install_directory(self) -> Optional[str]: + """ + Visual C compiler tools path. + :return: Path to the compiler tools + """ + if self._tools_install_directory is None: + + vc_version = float(int(self.version.split('.')[0])) + if vc_version >= 14.0: + vc_tools_path = self._installed_c_paths[vc_version]['root'] + else: + vc_tools_path = self._installed_c_paths[vc_version]['base'] + + # lib_path = os.path.join(vc_tools_path, 'lib') + tools_path = os.path.join(vc_tools_path, 'Tools', 'MSVC') + + if os.path.exists(tools_path): + versions = os.listdir(tools_path) + tools_version = None + + for version in versions: + if self._strict_toolkit_version is not None: + tk_version = ( + version[:len(self._strict_toolkit_version)] + ) + if tk_version == self._strict_toolkit_version: + tools_version = version + break + + if self._minimum_toolkit_version is not None: + tk_version = ( + version[:len(self._minimum_toolkit_version)] + ) + if tk_version < self._minimum_toolkit_version: + continue + + if tools_version is None: + tools_version = version + elif tools_version < version: + tools_version = version + + if tools_version is not None: + self._tools_install_directory = os.path.join( + tools_path, + tools_version + ) + + else: + raise RuntimeError('Unable to locate build tools') + + else: + raise RuntimeError('Unable to locate build tools') + + return self._tools_install_directory + + @property + def msbuild_version(self) -> Optional[str]: + """ + MSBuild versions are specific to the Visual C version + :return: MSBuild version, 3.5, 4.0, 12, 14, 15 + """ + if self._msbuild_version is None: + vc_version = str(float(int(self.version.split('.')[0]))) + + if vc_version == 9.0: + self._msbuild_version = '3.5' + elif vc_version in (10.0, 11.0): + self._msbuild_version = '4.0' + else: + self._msbuild_version = vc_version + return self._msbuild_version + + @property + def msbuild_path(self) -> Optional[str]: + if self._msbuild_path is not None: + program_files = os.environ.get( + 'ProgramFiles(x86)', + 'C:\\Program Files (x86)' + ) + + version = float(int(self.version.split('.')[0])) + + ms_build_path = os.path.join( + program_files, + 'MSBuild', + '{0:.1f}'.format(version), + 'bin' + ) + + if self.platform == 'x64': + if os.path.exists(os.path.join(ms_build_path, 'x64')): + ms_build_path = os.path.join(ms_build_path, 'x64') + else: + ms_build_path = os.path.join(ms_build_path, 'amd64') + + elif os.path.exists(os.path.join(ms_build_path, 'x86')): + ms_build_path = os.path.join(ms_build_path, 'x86') + + if os.path.exists(ms_build_path): + self._msbuild_path = ms_build_path + + return self._msbuild_path + + @property + def html_help_path(self) -> Optional[str]: + + reg_path = ( + winreg.HKEY_LOCAL_MACHINE, + r'SOFTWARE\Wow6432Node\Microsoft\Windows' + r'\CurrentVersion\App Paths\hhw.exe' + ) + + html_help_path = _get_reg_value(reg_path, 'Path') + if html_help_path and os.path.exists(html_help_path): + return html_help_path + + if os.path.exists( + r'C:\Program Files (x86)\HTML Help Workshop' + ): + return r'C:\Program Files (x86)\HTML Help Workshop' + + @property + def path(self) -> list: + tools_path = self.tools_install_directory + base_path = os.path.join(tools_path, 'bin') + + path = [] + + f_sharp_path = self.f_sharp_path + msbuild_path = self.msbuild_path + + ide_base_path = os.path.split(self.install_directory)[0] + perf_tools_x64_path = os.path.join( + ide_base_path, + 'Team Tools', + 'Performance Tools', + 'x64' + ) + + if os.path.exists(perf_tools_x64_path): + path += [perf_tools_x64_path] + + perf_tools_path = os.path.join( + ide_base_path, + 'Team Tools', + 'Performance Tools' + ) + if os.path.exists(perf_tools_path): + path += [perf_tools_path] + + com7_ide_path = os.path.join( + ide_base_path, + 'Common7', + 'IDE' + ) + + vc_packages_path = os.path.join( + com7_ide_path, + 'VC', + 'VCPackages' + ) + if os.path.exists(vc_packages_path): + path += [vc_packages_path] + + team_explorer_path = os.path.join( + com7_ide_path, + 'CommonExtensions', + 'Microsoft', + 'TeamFoundation', + 'Team Explorer' + ) + if os.path.exists(team_explorer_path): + path += [team_explorer_path] + + intellicode_cli_path = os.path.join( + com7_ide_path, + 'Extensions', + 'Microsoft', + 'IntelliCode', + 'CLI' + ) + if os.path.exists(intellicode_cli_path): + path += [intellicode_cli_path] + + roslyn_path = os.path.join( + ide_base_path, + 'MSBuild', + 'Current', + 'bin', + 'Roslyn' + ) + if os.path.exists(roslyn_path): + path += [roslyn_path] + + devinit_path = os.path.join( + ide_base_path, + 'Common7', + 'Tools', + 'devinit' + ) + if os.path.exists(devinit_path): + path += [devinit_path] + + vs_path = os.path.split(ide_base_path)[0] + vs_path, edition = os.path.split(vs_path) + + collection_tools_path = os.path.join( + vs_path, + 'Shared', + 'Common', + 'VSPerfCollectionTools', + 'vs' + edition + ) + if os.path.exists(collection_tools_path): + path += [collection_tools_path] + + collection_tools_path_x64 = os.path.join( + collection_tools_path, + 'x64' + ) + if os.path.exists(collection_tools_path_x64): + path += [collection_tools_path_x64] + + if msbuild_path is not None: + path += [os.path.split(msbuild_path)[0]] + + if f_sharp_path is not None: + path += [f_sharp_path] + + html_help_path = self.html_help_path + if html_help_path is not None: + path += [html_help_path] + + bin_path = os.path.join( + base_path, + 'Host' + self.platform, + self.platform + ) + + if not os.path.exists(bin_path): + if self.platform == 'x64': + bin_path = os.path.join(base_path, 'x64') + if not os.path.exists(bin_path): + bin_path = os.path.join(base_path, 'amd64') + else: + bin_path = os.path.join(base_path, 'x86') + if not os.path.exists(bin_path): + bin_path = base_path + + if os.path.exists(bin_path): + path += [bin_path] + + path += self.cmake_paths + + return path + + @property + def atlmfc_lib_path(self) -> Optional[str]: + atlmfc_path = self.atlmfc_path + if not atlmfc_path: + return + + atlmfc = os.path.join(atlmfc_path, 'lib') + if self.platform == 'x64': + atlmfc_path = os.path.join(atlmfc, 'x64') + if not os.path.exists(atlmfc_path): + atlmfc_path = os.path.join(atlmfc, 'amd64') + else: + atlmfc_path = os.path.join(atlmfc, 'x86') + if not os.path.exists(atlmfc_path): + atlmfc_path = atlmfc + + if os.path.exists(atlmfc_path): + return atlmfc_path + + @property + def lib(self) -> list: + tools_path = self.tools_install_directory + path = os.path.join(tools_path, 'lib') + + if self.platform == 'x64': + lib_path = os.path.join(path, 'x64') + if not os.path.exists(lib_path): + lib_path = os.path.join(path, 'amd64') + + else: + lib_path = os.path.join(path, 'x86') + + if not os.path.exists(lib_path): + lib_path = path + + lib = [] + if os.path.exists(lib_path): + lib += [lib_path] + + atlmfc_path = self.atlmfc_lib_path + if atlmfc_path is not None: + lib += [atlmfc_path] + + return lib + + @property + def lib_path(self) -> list: + tools_path = self.tools_install_directory + path = os.path.join(tools_path, 'lib') + + if self.platform == 'x64': + lib = os.path.join(path, 'x64') + if not os.path.exists(lib): + lib = os.path.join(path, 'amd64') + else: + lib = os.path.join(path, 'x86') + if not os.path.exists(lib): + lib = path + + references_path = os.path.join( + lib, + 'store', + 'references' + ) + + lib_path = [] + if os.path.exists(lib): + lib_path += [lib] + + atlmfc_path = self.atlmfc_lib_path + + if atlmfc_path is not None: + lib_path += [atlmfc_path] + + if os.path.exists(references_path): + lib_path += [references_path] + + return lib_path + + @property + def atlmfc_path(self) -> Optional[str]: + tools_path = self.tools_install_directory + atlmfc_path = os.path.join(tools_path, 'ATLMFC') + + if os.path.exists(atlmfc_path): + return atlmfc_path + + @property + def atlmfc_include_path(self) -> Optional[str]: + atlmfc_path = self.atlmfc_path + if atlmfc_path is None: + return + + atlmfc_include_path = os.path.join( + atlmfc_path, + 'include' + ) + if os.path.exists(atlmfc_include_path): + return atlmfc_include_path + + @property + def include(self) -> list: + tools_path = self.tools_install_directory + include_path = os.path.join(tools_path, 'include') + atlmfc_path = self.atlmfc_include_path + + include = [] + if os.path.exists(include_path): + include += [include_path] + + if atlmfc_path is not None: + include += [atlmfc_path] + return include + + def __iter__(self): + ide_install_directory = self.ide_install_directory + tools_install_directory = self.tools_install_directory + install_directory = self.install_directory + + if ide_install_directory: + ide_install_directory += '\\' + + if tools_install_directory: + tools_install_directory += '\\' + + if install_directory: + install_directory += '\\' + + env = dict( + VCIDEInstallDir=ide_install_directory, + VCToolsVersion=self.tools_version, + VCToolsInstallDir=tools_install_directory, + VCINSTALLDIR=install_directory, + VCToolsRedistDir=self.tools_redist_directory, + Path=self.path, + LIB=self.lib, + Include=self.include, + LIBPATH=self.lib_path, + FSHARPINSTALLDIR=self.f_sharp_path + ) + + if self._product_semantic_version is not None: + env['VSCMD_VER'] = self._product_semantic_version + + if self._devinit_path is not None: + env['__devinit_path'] = self._devinit_path + + for key, value in env.items(): + if value is not None and value: + if isinstance(value, list): + value = os.pathsep.join(value) + yield key, str(value) + + def __str__(self): + template = ( + '== Visual C ===================================================\n' + ' version: {visual_c_version}\n' + ' path: {visual_c_path}\n' + '\n' + ' -- Tools ---------------------------------------------------\n' + ' version: {tools_version}\n' + ' path: {tools_install_path}\n' + ' redist path: {vc_tools_redist_path}\n' + ' -- F# ------------------------------------------------------\n' + ' path: {f_sharp_path}\n' + ' -- DLL -----------------------------------------------------\n' + ' version: {platform_toolset}-{msvc_dll_version}\n' + ' path: {msvc_dll_path}\n' + '\n' + '== MSBuild ====================================================\n' + ' version: {msbuild_version}\n' + ' path: {msbuild_path}\n' + '\n' + '== HTML Help ==================================================\n' + ' path: {html_help_path}\n' + '\n' + '== ATLMFC =====================================================\n' + ' path: {atlmfc_path}\n' + ' include path: {atlmfc_include_path}\n' + ' lib path: {atlmfc_lib_path}\n' + ) + + return template.format( + visual_c_version=self.version, + visual_c_path=self.install_directory, + tools_version=self.tools_version, + tools_install_path=self.tools_install_directory, + vc_tools_redist_path=self.tools_redist_directory, + platform_toolset=self.toolset_version, + msvc_dll_version=self.msvc_dll_version, + msvc_dll_path=self.msvc_dll_path, + msbuild_version=self.msbuild_version, + msbuild_path=self.msbuild_path, + f_sharp_path=self.f_sharp_path, + html_help_path=self.html_help_path, + atlmfc_lib_path=self.atlmfc_lib_path, + atlmfc_include_path=self.atlmfc_include_path, + atlmfc_path=self.atlmfc_path, + ) + + +class VisualStudioInfo(object): + + def __init__( + self, + environ: Environment, + c_info: VisualCInfo + ): + self.environment = environ + self.__devenv_version = None + self.c_info = c_info + self._install_directory = None + self._dev_env_directory = None + self._common_tools = None + + installation = c_info.cpp_installation + + self.__name__ = 'VisualStudioInfo' + + if installation is not None: + if installation.path.endswith('BuildTools'): + self.__name__ = 'BuildToolsInfo' + version = installation.version.split('.')[0] + self.__devenv_version = ( + str(float(int(version))), + installation.version + ) + + install_directory = installation.path + if os.path.exists(install_directory): + self._install_directory = install_directory + + dev_env_directory = os.path.join( + install_directory, + os.path.split(installation.product_path)[0] + ) + if os.path.exists(dev_env_directory): + self._dev_env_directory = dev_env_directory + + common_tools = os.path.join( + install_directory, 'Common7', 'Tools' + ) + if os.path.exists(common_tools): + self._common_tools = common_tools + '\\' + + @property + def install_directory(self) -> str: + if self._install_directory is None: + install_dir = os.path.join( + self.c_info.install_directory, + '..' + ) + + self._install_directory = ( + os.path.abspath(install_dir) + ) + return self._install_directory + + @property + def dev_env_directory(self) -> str: + if self._dev_env_directory is None: + self._dev_env_directory = os.path.join( + self.install_directory, + 'Common7', + 'IDE' + ) + + return self._dev_env_directory + + @property + def common_tools(self) -> str: + if self._common_tools is None: + + common_tools = os.path.join( + self.install_directory, + 'Common7', + 'Tools' + ) + if os.path.exists(common_tools): + self._common_tools = common_tools + '\\' + else: + self._common_tools = '' + + return self._common_tools + + @property + def path(self) -> list: + path = [] + + dev_env_directory = self.dev_env_directory + if dev_env_directory: + path.append(dev_env_directory) + + common_tools = self.common_tools + + if common_tools: + path.append(common_tools[:-1]) + + collection_tools_dir = _get_reg_value( + 'VisualStudio\\VSPerf', + 'CollectionToolsDir' + ) + if ( + collection_tools_dir and + os.path.exists(collection_tools_dir) + ): + path.append(collection_tools_dir) + + vs_ide_path = self.dev_env_directory + + test_window_path = os.path.join( + vs_ide_path, + 'CommonExtensions', + 'Microsoft', + 'TestWindow' + ) + + vs_tdb_path = os.path.join( + vs_ide_path, + 'VSTSDB', + 'Deploy' + ) + + if os.path.exists(vs_tdb_path): + path.append(vs_tdb_path) + + if os.path.exists(test_window_path): + path.append(test_window_path) + + return path + + @property + def __version(self): + if not isinstance(self.__devenv_version, tuple): + dev_env_dir = self.dev_env_directory + + if dev_env_dir is not None: + command = ''.join([ + '"', + os.path.join(dev_env_dir, 'devenv'), + '" /?\n' + ]) + + proc = subprocess.Popen( + 'cmd', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + shell=True + ) + + proc.stdin.write(command.encode('utf-8')) + out, err = proc.communicate() + proc.stdin.close() + + err = err.strip() + + if err: + self.__devenv_version = (None, None) + return self.__devenv_version + + for line in out.decode('utf-8').split('\n'): + if ' Visual Studio ' in line: + break + else: + self.__devenv_version = (None, None) + return self.__devenv_version + + line = line.rstrip('.').strip() + line = line.split(' Visual Studio ')[-1] + + common_version, version = line.split(' Version ') + + self.__devenv_version = (common_version, version) + else: + self.__devenv_version = (None, None) + + return self.__devenv_version + + @property + def common_version(self) -> Optional[str]: + return self.__version[0] + + @property + def uncommon_version(self) -> Optional[str]: + return self.__version[1] + + @property + def version(self) -> Optional[float]: + version = self.uncommon_version + + if version is not None: + return float(int(version.split('.')[0])) + + def __iter__(self): + install_directory = self.install_directory + dev_env_directory = self.dev_env_directory + + if install_directory: + install_directory += '\\' + + if dev_env_directory: + dev_env_directory += '\\' + + env = dict( + Path=self.path, + VSINSTALLDIR=install_directory, + DevEnvDir=dev_env_directory, + VisualStudioVersion=self.version + ) + + toolsets = { + 'v142': '160', + 'v141': '150', + 'v140': '140', + 'v120': '120', + 'v110': '110', + 'v100': '100', + 'v90': '90' + } + + toolset_version = self.c_info.toolset_version + + if toolset_version in toolsets: + comn_tools = 'VS{0}COMNTOOLS'.format( + toolsets[toolset_version] + ) + env[comn_tools] = self.common_tools + + for key, value in env.items(): + if value is not None and value: + if isinstance(value, list): + value = os.pathsep.join(value) + yield key, str(value) + + def __str__(self): + installation = self.c_info.cpp_installation + + if installation is None: + return '' + + template = ( + '== {name}==============================================\n' + ' version: {version}\n' + ' version (friendly): {product_line_version}\n' + ' display version: {product_display_version}\n' + ' path: {path}\n' + ' executable: {product_path}\n' + ' is complete: {is_complete}\n' + ' is prerelease: {is_prerelease}\n' + ' is launchable: {is_launchable}\n' + ' state: {state}\n' + ) + + if self.__name__ == 'VisualStudioInfo': + name = 'Visual Studio ' + else: + name = 'Build Tools ==' + + path = installation.path + version = installation.version + is_complete = installation.is_complete + is_prerelease = installation.is_prerelease + is_launchable = installation.is_launchable + state = ', '.join(installation.state) + product_path = os.path.join(path, installation.product_path) + + catalog = installation.catalog + product_display_version = catalog.product_display_version + product_line_version = catalog.product_line_version + + return template.format( + name=name, + path=path, + version=version, + is_complete=is_complete, + is_prerelease=is_prerelease, + is_launchable=is_launchable, + product_path=product_path, + product_display_version=product_display_version, + product_line_version=product_line_version, + state=state + ) + + +class WindowsSDKInfo(object): + + def __init__( + self, + environ: Environment, + c_info: VisualCInfo, + minimum_sdk_version: Optional[str] = None, + strict_sdk_version: Optional[str] = None + ): + self.environment = environ + self.c_info = c_info + self.platform = environ.platform + self.vc_version = c_info.version + + if ( + strict_sdk_version is not None and + strict_sdk_version.startswith('10.0') + ): + if strict_sdk_version.count('.') == 2: + strict_sdk_version += '.0' + + if ( + minimum_sdk_version is not None and + minimum_sdk_version.startswith('10.0') + ): + if minimum_sdk_version.count('.') == 2: + minimum_sdk_version += '.0' + + self._minimum_sdk_version = minimum_sdk_version + self._strict_sdk_version = strict_sdk_version + + self._directory = None + self._sdk_version = None + self._version = None + + @property + def extension_sdk_directory(self) -> Optional[str]: + version = self.version + + if version.startswith('10'): + extension_path = os.path.join( + _PROGRAM_FILES_X86, + 'Microsoft SDKs', + 'Windows Kits', + '10', + 'ExtensionSDKs' + ) + if os.path.exists(extension_path): + return extension_path + + sdk_path = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'InstallationFolder' + ) + + if sdk_path: + sdk_path = sdk_path.replace( + 'Windows Kits', + 'Microsoft SDKs\\Windows Kits' + ) + extension_path = os.path.join( + sdk_path[:-1], + 'Extension SDKs' + ) + + if os.path.exists(extension_path): + return extension_path + + @property + def lib_version(self) -> str: + return self.sdk_version + + @property + def ver_bin_path(self) -> str: + bin_path = self.bin_path[:-1] + + version = self.version + + if version == '10.0': + version = self.sdk_version + + ver_bin_path = os.path.join(bin_path, version) + if os.path.exists(ver_bin_path): + return ver_bin_path + else: + return bin_path + + @property + def mssdk(self) -> Optional[str]: + return self.directory + + @property + def ucrt_version(self) -> Optional[str]: + if self.version == '10.0': + sdk_version = self.sdk_version + else: + sdk_versions = _read_reg_keys( + 'Microsoft SDKs\\Windows', + True + ) + if 'v10.0' in sdk_versions: + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'ProductVersion', + True + ) + else: + return + + if sdk_version.endswith('0'): + return sdk_version + else: + return sdk_version[:-1] + + @property + def ucrt_lib_directory(self) -> Optional[str]: + if self.version == '10.0': + directory = self.directory + directory = os.path.join( + directory, + 'Lib', + self.sdk_version, + 'ucrt', + self.platform + ) + + else: + sdk_versions = _read_reg_keys( + 'Microsoft SDKs\\Windows', + True + ) + if 'v10.0' in sdk_versions: + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'ProductVersion', + True + ) + directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'InstallationFolder', + True + ) + directory = os.path.join( + directory, + 'Lib', + sdk_version, + 'ucrt', + self.platform + ) + else: + return + + if os.path.exists(directory): + if not directory.endswith('\\'): + return directory + '\\' + + return directory + + @property + def ucrt_headers_directory(self) -> Optional[str]: + if self.version == '10.0': + directory = self.directory + directory = os.path.join( + directory, + 'Include', + self.sdk_version, + 'ucrt' + ) + + else: + sdk_versions = _read_reg_keys( + 'Microsoft SDKs\\Windows', + True + ) + if 'v10.0' in sdk_versions: + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'ProductVersion', + True + ) + directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'InstallationFolder', + True + ) + directory = os.path.join( + directory, + 'Include', + sdk_version, + 'ucrt' + ) + else: + return + + if os.path.exists(directory): + if not directory.endswith('\\'): + return directory + '\\' + + return directory + + @property + def ucrt_sdk_directory(self) -> Optional[str]: + directory = self.directory + + if self.version != '10.0': + sdk_versions = _read_reg_keys( + 'Microsoft SDKs\\Windows', + True + ) + if 'v10.0' in sdk_versions: + directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v10.0', + 'InstallationFolder', + True + ) + else: + return + + if os.path.exists(directory): + if not directory.endswith('\\'): + return directory + '\\' + + return directory + + @property + def bin_path(self) -> Optional[str]: + directory = self.directory + if directory: + bin_path = os.path.join( + self.directory, + 'bin' + ) + + return bin_path + '\\' + + @property + def lib(self) -> list: + directory = self.directory + if not directory: + return [] + + version = self.version + if version == '10.0': + version = self.sdk_version + + lib = [] + + base_lib = os.path.join( + directory, + 'lib', + version, + ) + if not os.path.exists(base_lib): + base_lib = os.path.join( + directory, + 'lib' + ) + + if os.path.exists(base_lib): + if self.platform == 'x64': + if os.path.exists(os.path.join(base_lib, 'x64')): + lib += [os.path.join(base_lib, 'x64')] + + ucrt = os.path.join(base_lib, 'ucrt', self.platform) + um = os.path.join(base_lib, 'um', self.platform) + if not os.path.exists(ucrt): + ucrt = os.path.join(base_lib, 'ucrt', 'amd64') + + if not os.path.exists(um): + um = os.path.join(base_lib, 'um', 'amd64') + + else: + lib += [base_lib] + ucrt = os.path.join(base_lib, 'ucrt', self.platform) + um = os.path.join(base_lib, 'um', self.platform) + + if not os.path.exists(ucrt): + ucrt = os.path.join(base_lib, 'ucrt') + + if not os.path.exists(um): + um = os.path.join(base_lib, 'um') + + if os.path.exists(ucrt): + lib += [ucrt] + else: + ucrt = self.ucrt_lib_directory + if ucrt is not None: + lib += [ucrt] + + if os.path.exists(um): + lib += [um] + + return lib + + @property + def path(self) -> list: + path = [] + ver_bin_path = self.ver_bin_path + + if self.platform == 'x64': + bin_path = os.path.join(ver_bin_path, 'x64') + if not os.path.exists(bin_path): + bin_path = os.path.join( + ver_bin_path, + 'amd64' + ) + else: + bin_path = os.path.join(ver_bin_path, 'x86') + + if not os.path.exists(bin_path): + bin_path = ver_bin_path + + if os.path.exists(bin_path): + path += [bin_path] + + bin_path = self.bin_path + bin_path = os.path.join( + bin_path, + 'x64' + ) + + if os.path.exists(bin_path): + path += [bin_path] + + type_script_path = self.type_script_path + if type_script_path is not None: + path += [type_script_path] + + return path + + @property + def type_script_path(self) -> Optional[str]: + program_files = os.environ.get( + 'ProgramFiles(x86)', + 'C:\\Program Files (x86)' + ) + type_script_path = os.path.join( + program_files, + 'Microsoft SDKs', + 'TypeScript' + ) + + if os.path.exists(type_script_path): + max_ver = 0.0 + for version in os.listdir(type_script_path): + try: + version = float(version) + except ValueError: + continue + max_ver = max(max_ver, version) + + type_script_path = os.path.join( + type_script_path, + str(max_ver) + ) + + if os.path.exists(type_script_path): + return type_script_path + + @property + def include(self) -> list: + directory = self.directory + if self.version == '10.0': + include_path = os.path.join( + directory, + 'include', + self.sdk_version + ) + else: + include_path = os.path.join( + directory, + 'include' + ) + + includes = [include_path] + + for path in ('ucrt', 'cppwinrt', 'shared', 'um', 'winrt'): + pth = os.path.join(include_path, path) + if os.path.exists(pth): + includes += [pth] + elif path == 'ucrt' and self.version != '10.0': + ucrt = self.ucrt_headers_directory + if ucrt is not None: + includes += [ucrt] + + gl_include = os.path.join(include_path, 'gl') + + if os.path.exists(gl_include): + includes += [gl_include] + + return includes + + @property + def lib_path(self) -> list: + return self.sdk_lib_path + + @property + def sdk_lib_path(self) -> list: + directory = self.directory + version = self.version + + if version == '10.0': + version = self.sdk_version + + union_meta_data = os.path.join( + directory, + 'UnionMetadata', + version + ) + references = os.path.join( + directory, + 'References', + version + ) + + lib_path = [] + + if os.path.exists(union_meta_data): + lib_path += [union_meta_data] + + if os.path.exists(references): + lib_path += [references] + + return lib_path + + @property + def windows_sdks(self) -> list: + """ + Windows SDK versions that are compatible with Visual C + :return: compatible Windows SDK versions + """ + ver = int(self.vc_version.split('.')[0]) + + sdk_versions = [] + if ver >= 14: + sdk_versions.extend(['v10.0']) + if ver >= 12: + sdk_versions.extend(['v8.1a', 'v8.1']) + if ver >= 11: + sdk_versions.extend(['v8.0a', 'v8.0']) + if ver >= 10: + sdk_versions.extend(['v7.1a', 'v7.1', 'v7.0a']) + + sdk_versions.extend(['v7.0', 'v6.1', 'v6.0a']) + + if ( + self._minimum_sdk_version is not None and + self._minimum_sdk_version in sdk_versions + ): + index = sdk_versions.index(self._minimum_sdk_version) + sdk_versions = sdk_versions[:index + 1] + + return sdk_versions + + @property + def version(self) -> str: + """ + This is used in the solution file to tell the compiler what SDK to use. + We obtain a list of compatible Windows SDK versions for the + Visual C version. We check and see if any of the compatible SDK's are + installed and if so we return that version. + + :return: Installed Windows SDK version + """ + + if self._version is None: + sdk_versions = _read_reg_keys('Microsoft SDKs\\Windows', True) + if self._strict_sdk_version is not None: + if self._strict_sdk_version.startswith('10.0'): + keys = _read_reg_keys('Windows Kits\\Installed Roots') + if self._strict_sdk_version in keys: + self._version = '10.0' + return self._version + + raise RuntimeError( + 'Unable to locate Windows SDK ' + + self._strict_sdk_version + ) + + if 'v' + self._strict_sdk_version in sdk_versions: + self._version = self._strict_sdk_version + return self._version + + for sdk in self.windows_sdks: + if sdk in sdk_versions: + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\' + sdk, + 'ProductVersion', + True + ) + + if sdk_version.startswith('10.0'): + while sdk_version.count('.') < 3: + sdk_version += '.0' + + if self._strict_sdk_version is not None: + if self._strict_sdk_version == sdk_version: + self._version = sdk[1:] + return self._version + + continue + + if self._minimum_sdk_version is not None: + if self._minimum_sdk_version.count('.') > 1: + if sdk_version < self._minimum_sdk_version: + break + + self._version = sdk[1:] + return self._version + + if self._strict_sdk_version is not None: + raise RuntimeError( + 'Unable to locate Windows SDK version ' + + self._strict_sdk_version + ) + + if self._minimum_sdk_version is not None: + raise RuntimeError( + 'Unable to locate Windows SDK vesion >= ' + + self._minimum_sdk_version + ) + + raise RuntimeError('Unable to locate Windows SDK') + + return self._version + + @property + def sdk_version(self) -> str: + """ + This is almost identical to target_platform. Except it returns the + actual version of the Windows SDK not the truncated version. + + :return: actual Windows SDK version + """ + + if self._sdk_version is None: + version = self.version + + if self._strict_sdk_version is None: + self._sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'ProductVersion', + True + ) + if self._sdk_version.startswith('10.0'): + while self._sdk_version.count('.') < 3: + self._sdk_version += '.0' + + return self._sdk_version + + if self._strict_sdk_version == version: + self._sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'ProductVersion', + True + ) + + if self._sdk_version.startswith('10.0'): + while self._sdk_version.count('.') < 3: + self._sdk_version += '.0' + + return self._sdk_version + + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'ProductVersion', + True + ) + + if sdk_version.startswith('10.0'): + while sdk_version.count('.') < 3: + sdk_version += '.0' + + if self._strict_sdk_version == sdk_version: + self._sdk_version = sdk_version + return self._sdk_version + + if self._strict_sdk_version.startswith('10.0'): + keys = _read_reg_keys('Windows Kits\\Installed Roots') + if self._strict_sdk_version in keys: + self._sdk_version = self._strict_sdk_version + return self._sdk_version + + return self._sdk_version + + @property + def directory(self) -> Optional[str]: + """ + Path to the Windows SDK version that has been found. + :return: Windows SDK path + """ + + if self._directory is None: + version = self.version + + if self._strict_sdk_version is None: + self._directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'InstallationFolder', + True + ) + return self._directory + + if self._strict_sdk_version == version: + self._directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'InstallationFolder', + True + ) + return self._directory + + sdk_version = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'ProductVersion' + ) + + if sdk_version.startswith('10.0'): + while sdk_version.count('.') < 3: + sdk_version += '.0' + + if self._strict_sdk_version == sdk_version: + self._directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'InstallationFolder', + True + ) + return self._directory + + if self._strict_sdk_version.startswith('10.0'): + keys = _read_reg_keys('Windows Kits\\Installed Roots') + if self._strict_sdk_version in keys: + self._directory = _get_reg_value( + 'Microsoft SDKs\\Windows\\v' + version, + 'InstallationFolder', + True + ) + + return self._directory + + return self._directory + + def __iter__(self): + + ver_bin_path = self.ver_bin_path + directory = self.directory + + if ver_bin_path: + ver_bin_path += '\\' + + if directory and not directory.endswith('\\'): + directory += '\\' + + lib_version = self.lib_version + if not lib_version.endswith('\\'): + lib_version += '\\' + + sdk_version = self.sdk_version + if not sdk_version.endswith('\\'): + sdk_version += '\\' + + env = dict( + LIB=self.lib, + Path=self.path, + LIBPATH=self.lib_path, + Include=self.include, + UniversalCRTSdkDir=self.ucrt_sdk_directory, + ExtensionSdkDir=self.extension_sdk_directory, + WindowsSdkVerBinPath=ver_bin_path, + UCRTVersion=self.ucrt_version, + WindowsSDKLibVersion=lib_version, + WindowsSDKVersion=sdk_version, + WindowsSdkDir=directory, + WindowsLibPath=self.lib_path, + WindowsSdkBinPath=self.bin_path, + DISTUTILS_USE_SDK=1, + MSSDK=self.directory + ) + + for key, value in env.items(): + if value is not None and value: + if isinstance(value, list): + value = os.pathsep.join(value) + yield key, str(value) + + def __str__(self): + template = ( + '== Windows SDK ================================================\n' + ' version: {target_platform}\n' + ' sdk version: {windows_sdk_version}\n' + ' path: {target_platform_path}\n' + '\n' + '== Universal CRT ==============================================\n' + ' version: {ucrt_version}\n' + ' path: {ucrt_sdk_directory}\n' + ' lib directory: {ucrt_lib_directory}\n' + ' headers directory: {ucrt_headers_directory}\n' + '\n' + '== Extension SDK ==============================================\n' + ' path: {extension_sdk_directory}\n' + '\n' + '== TypeScript =================================================\n' + ' path: {type_script_path}\n' + ) + + return template.format( + target_platform=self.version, + windows_sdk_version=self.sdk_version, + target_platform_path=self.directory, + extension_sdk_directory=self.extension_sdk_directory, + ucrt_sdk_directory=self.ucrt_sdk_directory, + ucrt_headers_directory=self.ucrt_headers_directory, + ucrt_lib_directory=self.ucrt_lib_directory, + ucrt_version=self.ucrt_version, + type_script_path=self.type_script_path, + ) + + +class NETInfo(object): + + def __init__( + self, + environ: Environment, + c_info: VisualCInfo, + sdk_version: str, + minimum_net_version: Optional[str] = None, + strict_net_version: Optional[str] = None + ): + + self.environment = environ + self.platform = environ.platform + self.c_info = c_info + self.vc_version = c_info.version + self.sdk_version = sdk_version + self._minimum_net_version = minimum_net_version + self._strict_net_version = strict_net_version + self._version_32 = None + self._version_64 = None + + @property + def version(self) -> str: + """ + .NET Version + :return: returns the version associated with the architecture + """ + if self.platform == 'x64': + return self.version_64 + else: + return self.version_32 + + @property + def version_32(self) -> str: + """ + .NET 32bit framework version + :return: x86 .NET framework version + """ + if self._version_32 is None: + target_framework = None + installation = self.c_info.cpp_installation + + if installation is not None: + for package in installation.packages.msi: + if ( + package.id.startswith('Microsoft.Net') and + package.id.endswith('TargetingPack') + ): + + if self._strict_net_version is not None: + if self._strict_net_version == package: + self._version_32 = 'v' + package.version + return self._version_32 + else: + continue + + if self._minimum_net_version is not None: + if package < self._minimum_net_version: + continue + + if target_framework is None: + target_framework = package + elif package > target_framework: + target_framework = package + + if target_framework is not None: + self._version_32 = 'v' + target_framework.version + return self._version_32 + + target_framework = _get_reg_value( + 'VisualStudio\\SxS\\VC7', + 'FrameworkVer32' + ) + + if target_framework: + if self._strict_net_version is not None: + if target_framework[1:] == self._strict_net_version: + self._version_32 = target_framework + return self._version_32 + elif self._minimum_net_version is not None: + if target_framework[1:] >= self._strict_net_version: + self._version_32 = target_framework + return self._version_32 + else: + self._version_32 = target_framework + return self._version_32 + + versions = list( + key for key in _read_reg_keys('.NETFramework\\', True) + if key.startswith('v') + ) + + target_framework = None + + if self._strict_net_version is not None: + if 'v' + self._strict_net_version in versions: + self._version_32 = 'v' + self._strict_net_version + return self._version_32 + else: + for version in versions: + if self._minimum_net_version is not None: + if version[1:] < self._minimum_net_version: + continue + + if target_framework is None: + target_framework = version[1:] + elif target_framework < version[1:]: + target_framework = version[1:] + + if target_framework is not None: + self._version_32 = 'v' + target_framework + return self._version_32 + + net_path = os.path.join( + '%SystemRoot%', + 'Microsoft.NET', + 'Framework' + ) + net_path = os.path.expandvars(net_path) + if os.path.exists(net_path): + versions = [item[1:] for item in os.listdir(net_path)] + if self._strict_net_version is not None: + if self._strict_net_version in versions: + self._version_32 = 'v' + self._strict_net_version + return self._version_32 + + raise RuntimeError( + 'Unable to locate .NET version ' + + self._strict_net_version + ) + + for version in versions: + if ( + self._minimum_net_version is not None and + version < self._minimum_net_version + ): + continue + + if target_framework is None: + target_framework = version + elif target_framework < version: + target_framework = version + + if target_framework: + self._version_32 = 'v' + target_framework + return self._version_32 + + if self._strict_net_version is not None: + raise RuntimeError( + 'Unable to locate .NET version ' + + self._strict_net_version + ) + + if self._minimum_net_version is not None: + raise RuntimeError( + 'Unable to locate .NET version ' + + self._minimum_net_version + + ' or above.' + ) + + self._version_32 = '' + + return self._version_32 + + @property + def version_64(self) -> str: + """ + .NET 64bit framework version + :return: x64 .NET framework version + """ + + if self._version_64 is None: + target_framework = None + installation = self.c_info.cpp_installation + + if installation is not None: + for package in installation.packages.msi: + if ( + package.id.startswith('Microsoft.Net') and + package.id.endswith('TargetingPack') + ): + + if self._strict_net_version is not None: + if self._strict_net_version == package: + self._version_64 = 'v' + package.version + return self._version_64 + else: + continue + + if self._minimum_net_version is not None: + if package < self._minimum_net_version: + continue + + if target_framework is None: + target_framework = package + elif package > target_framework: + target_framework = package + + if target_framework is not None: + self._version_64 = 'v' + target_framework.version + return self._version_64 + + target_framework = _get_reg_value( + 'VisualStudio\\SxS\\VC7', + 'FrameworkVer64' + ) + + if target_framework: + if self._strict_net_version is not None: + if target_framework[1:] == self._strict_net_version: + self._version_64 = target_framework + return self._version_64 + + elif self._minimum_net_version is not None: + if target_framework[1:] >= self._strict_net_version: + self._version_64 = target_framework + return self._version_64 + + else: + self._version_64 = target_framework + return self._version_64 + + versions = list( + key for key in _read_reg_keys('.NETFramework\\', True) + if key.startswith('v') + ) + + target_framework = None + + if self._strict_net_version is not None: + if 'v' + self._strict_net_version in versions: + self._version_64 = 'v' + self._strict_net_version + return self._version_64 + else: + for version in versions: + if self._minimum_net_version is not None: + if version[1:] < self._minimum_net_version: + continue + + if target_framework is None: + target_framework = version[1:] + elif target_framework < version[1:]: + target_framework = version[1:] + + if target_framework is not None: + self._version_64 = 'v' + target_framework + return self._version_64 + + net_path = os.path.join( + '%SystemRoot%', + 'Microsoft.NET', + 'Framework64' + ) + net_path = os.path.expandvars(net_path) + if os.path.exists(net_path): + versions = [item[1:] for item in os.listdir(net_path)] + if self._strict_net_version is not None: + if self._strict_net_version in versions: + self._version_64 = 'v' + self._strict_net_version + return self._version_64 + + raise RuntimeError( + 'Unable to locate .NET version ' + + self._strict_net_version + ) + + for version in versions: + if ( + self._minimum_net_version is not None and + version < self._minimum_net_version + ): + continue + + if target_framework is None: + target_framework = version + elif target_framework < version: + target_framework = version + + if target_framework: + self._version_64 = 'v' + target_framework + return self._version_64 + + if self._strict_net_version is not None: + raise RuntimeError( + 'Unable to locate .NET version ' + + self._strict_net_version + ) + + if self._minimum_net_version is not None: + raise RuntimeError( + 'Unable to locate .NET version ' + + self._minimum_net_version + + ' or above.' + ) + + self._version_64 = '' + + return self._version_64 + + @property + def directory(self) -> str: + if self.platform == 'x64': + return self.directory_64 + else: + return self.directory_32 + + @property + def directory_32(self) -> str: + """ + .NET 32bit path + :return: path to x86 .NET + """ + directory = _get_reg_value( + 'VisualStudio\\SxS\\VC7\\', + 'FrameworkDir32' + ) + if not directory: + directory = os.path.join( + '%SystemRoot%', + 'Microsoft.NET', + 'Framework' + ) + directory = os.path.expandvars(directory) + else: + directory = directory[:-1] + + if os.path.exists(directory): + return directory + + return '' + + @property + def directory_64(self) -> str: + """ + .NET 64bit path + :return: path to x64 .NET + """ + + directory = _get_reg_value( + 'VisualStudio\\SxS\\VC7\\', + 'FrameworkDir64' + ) + if not directory: + directory = os.path.join( + '%SystemRoot%', + 'Microsoft.NET', + 'Framework64' + ) + directory = os.path.expandvars(directory) + else: + directory = directory[:-1] + + if os.path.exists(directory): + return directory + + return '' + + @property + def preferred_bitness(self) -> str: + return '32' if self.platform == 'x86' else '64' + + @property + def _net_fx_versions(self): + import fnmatch + + framework = self.version[1:].split('.')[:2] + net_fx_key = ( + 'WinSDK-NetFx{framework}Tools-{platform}' + ).format( + framework=''.join(framework), + platform=self.platform + ) + ver = float(int(self.vc_version.split('.')[0])) + + if ver in (9.0, 10.0, 11.0, 12.0): + key = 'Microsoft SDKs\\Windows\\v{0}\\{1}'.format( + self.sdk_version, + net_fx_key + ) + + if self.sdk_version in ('6.0A', '6.1'): + key = key.replace(net_fx_key, 'WinSDKNetFxTools') + + keys = (key,) + else: + keys = ( + 'Microsoft SDKs\\NETFXSDK\\3.5*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.0*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.5*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.6*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.7*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.8*\\' + net_fx_key, + 'Microsoft SDKs\\NETFXSDK\\4.6*\\' + net_fx_key, + 'Microsoft SDKs\\Windows\\v8.1\\' + net_fx_key, + 'Microsoft SDKs\\Windows\\v10.0\\' + net_fx_key, + ) + + for key in keys: + if '*' in key: + for fx_ver in _read_reg_keys('Microsoft SDKs\\NETFXSDK'): + fx_ver = 'Microsoft SDKs\\NETFXSDK\\{0}\\{1}'.format( + fx_ver, + net_fx_key + ) + + if fnmatch.fnmatch(fx_ver, key): + yield fx_ver + else: + val = _get_reg_value( + key + '\\', + 'InstallationFolder' + ) + if val: + yield val + + @property + def netfx_sdk_directory(self) -> Optional[str]: + for key in self._net_fx_versions: + net_fx_path = _get_reg_value( + key.rsplit('\\', 1)[0], + 'KitsInstallationFolder' + ) + + if net_fx_path and os.path.exists(net_fx_path): + return net_fx_path + + @property + def net_fx_tools_directory(self) -> Optional[str]: + for key in self._net_fx_versions: + net_fx_path = _get_reg_value(key, 'InstallationFolder') + + if net_fx_path and os.path.exists(net_fx_path): + return net_fx_path + + @property + def add(self) -> str: + return '__DOTNET_ADD_{0}BIT'.format(self.preferred_bitness) + + @property + def net_tools(self) -> list: + + version = float(int(self.vc_version.split('.')[0])) + if version <= 10.0: + include32 = True + include64 = self.platform == 'x64' + else: + include32 = self.platform == 'x86' + include64 = self.platform == 'x64' + + tools = [] + if include32: + tools += [ + os.path.join(self.directory_32, self.version_32) + ] + if include64: + tools += [ + os.path.join(self.directory_64, self.version_64) + ] + + return tools + + @property + def executable_path_x64(self) -> Optional[str]: + tools_directory = self.net_fx_tools_directory + if not tools_directory: + return + + if 'NETFX' in tools_directory: + if 'x64' in tools_directory: + return tools_directory + else: + tools_directory = os.path.join(tools_directory, 'x64') + if os.path.exists(tools_directory): + return tools_directory + + @property + def executable_path_x86(self) -> Optional[str]: + tools_directory = self.net_fx_tools_directory + if not tools_directory: + return + + if 'NETFX' in tools_directory: + if 'x64' in tools_directory: + return ( + os.path.split(os.path.split(tools_directory)[0])[0] + '\\' + ) + else: + return tools_directory + return None + + @property + def lib(self) -> list: + sdk_directory = self.netfx_sdk_directory + if not sdk_directory: + return [] + + sdk_directory = os.path.join(sdk_directory, 'lib', 'um') + + if self.platform == 'x64': + lib_dir = os.path.join(sdk_directory, 'x64') + if not os.path.exists(lib_dir): + lib_dir = os.path.join(sdk_directory, 'amd64') + else: + lib_dir = os.path.join(sdk_directory, 'x86') + if not os.path.exists(lib_dir): + lib_dir = sdk_directory + + if os.path.exists(lib_dir): + return [lib_dir] + + return [] + + @property + def path(self) -> list: + path = [] + directory = self.directory + + pth = os.path.join( + directory, + self.version + ) + + if os.path.exists(pth): + path += [pth] + else: + match = None + version = self.version + versions = [ + item[1:] for item in os.listdir(directory) + if item.startswith('v') + ] + for ver in versions: + if version > ver: + if match is None: + match = ver + elif ver > match: + match = ver + + if match is not None: + pth = os.path.join( + directory, + 'v' + match + ) + path += [pth] + + net_fx_tools = self.net_fx_tools_directory + if net_fx_tools: + path += [net_fx_tools] + + return path + + @property + def lib_path(self) -> list: + path = [] + directory = self.directory + + pth = os.path.join( + directory, + self.version + ) + + if os.path.exists(pth): + path += [pth] + else: + match = None + version = self.version + versions = [ + item[1:] for item in os.listdir(directory) + if item.startswith('v') + ] + for ver in versions: + if version > ver: + if match is None: + match = ver + elif ver > match: + match = ver + + if match is not None: + pth = os.path.join( + directory, + 'v' + match + ) + path += [pth] + + return path + + @property + def include(self) -> list: + net_fx_tools = self.net_fx_tools_directory + + if net_fx_tools: + net_fx_tools = os.path.join( + net_fx_tools, + 'include', + 'um' + ) + if os.path.exists(net_fx_tools): + return [net_fx_tools] + + return [] + + def __iter__(self): + directory = self.directory + if directory: + directory += '\\' + + env = dict( + WindowsSDK_ExecutablePath_x64=self.executable_path_x64, + WindowsSDK_ExecutablePath_x86=self.executable_path_x86, + LIB=self.lib, + Path=self.path, + LIBPATH=self.lib_path, + Include=self.include, + __DOTNET_PREFERRED_BITNESS=self.preferred_bitness, + FrameworkDir=directory, + FrameworkVersion=self.version, + NETFXSDKDir=self.netfx_sdk_directory, + ) + + env[self.add] = '1' + if self.platform == 'x64': + directory_64 = self.directory_64 + + if directory_64: + directory_64 += '\\' + + env['FrameworkDir64'] = directory_64 + env['FrameworkVersion64'] = self.version_64 + else: + directory_32 = self.directory_32 + if directory_32: + directory_32 += '\\' + + env['FrameworkDir32'] = directory_32 + env['FrameworkVersion32'] = self.version_32 + + framework = env['FrameworkVersion'][1:].split('.')[:2] + framework_version_key = ( + 'Framework{framework}Version'.format(framework=''.join(framework)) + ) + env[framework_version_key] = 'v' + '.'.join(framework) + + for key, value in env.items(): + if value is not None and value: + if isinstance(value, list): + value = os.pathsep.join(value) + yield key, str(value) + + def __str__(self): + template = ( + '== .NET =======================================================\n' + ' version: {target_framework}\n' + '\n' + ' -- x86 -----------------------------------------------------\n' + ' version: {framework_version_32}\n' + ' path: {framework_dir_32}\n' + ' -- x64 -----------------------------------------------------\n' + ' version: {framework_version_64}\n' + ' path: {framework_dir_64}\n' + ' -- NETFX ---------------------------------------------------\n' + ' path: {net_fx_tools_directory}\n' + ' x86 exe path: {executable_path_x86}\n' + ' x64 exe path: {executable_path_x64}\n' + ) + return template.format( + target_framework=self.version, + framework_version_32=self.version_32, + framework_dir_32=self.directory_32, + framework_version_64=self.version_64, + framework_dir_64=self.directory_64, + net_fx_tools_directory=self.net_fx_tools_directory, + executable_path_x64=self.executable_path_x64, + executable_path_x86=self.executable_path_x86, + ) + + +def setup_environment( + minimum_c_version: Optional[Union[int, float]] = None, + strict_c_version: Optional[Union[int, float]] = None, + minimum_toolkit_version: Optional[int] = None, + strict_toolkit_version: Optional[int] = None, + minimum_sdk_version: Optional[str] = None, + strict_sdk_version: Optional[str] = None, + minimum_net_version: Optional[str] = None, + strict_net_version: Optional[str] = None, + vs_version: Optional[Union[str, int]] = None +): + """ + Main entry point. + + :param minimum_c_version: The lowest MSVC compiler version to allow. + :type minimum_c_version: optional - int, float + + :param strict_c_version: The MSVC compiler version that MUST be used. + ie 14.0, 14.2 + :type strict_c_version: optional - int or float + + :param minimum_toolkit_version: The lowest build tools version to allow. + :type minimum_toolkit_version: optional - int + + :param strict_toolkit_version: The build tools version that MUST be used. + ie 142, 143 + :type strict_toolkit_version: optional - int + + :param minimum_sdk_version: The lowest SDK version to allow. + This can work several ways, if you want to specify a specific version as + the minimum `"10.0.22000.0"` or if you want to make sure that only + Windows 10 SDK's get used `"10.0"` + :type minimum_sdk_version: optional - str + + :param strict_sdk_version: The Windows SDK that MUST be used. + Whole version only. + :type strict_sdk_version: optional - str + + :param minimum_net_version: Works the same as minimum_sdk_version + :type minimum_net_version: optional - str + + :param strict_net_version: works the same as strict_sdk_version + :type strict_net_version: optional - str + + :param vs_version: The version of visual studio you want to use. + This can be one of the following version types. + + * version: `str("16.10.31515.178")` + * display version:`str("16.10.4")` + * product line version: `int(2019)`. + + If you have 2 installations that share the same product line version + the installation with the higher version will get used. An example of + this is Visual Studio 20019 and Build Tools 2019. If you want to specify + a specific installation in this case then use the version or + display version options. + :type vs_version: optional - str, int + + :return: Environment instance + :rtype: Environment + """ + if not _IS_WIN: + raise RuntimeError( + 'This script will only work with a Windows opperating system.' + ) + + distutils.log.debug( + 'Setting up Windows build environment, please wait.....' + ) + + python_version = sys.version_info[:2] + if minimum_c_version is None: + if python_version == (3, 10): + minimum_c_version = 14.2 + elif python_version == (3, 9): + minimum_c_version = 14.2 + elif python_version == (3, 8): + minimum_c_version = 14.0 + elif python_version == (3, 7): + minimum_c_version = 14.0 + elif python_version == (3, 6): + minimum_c_version = 14.0 + elif python_version == (3, 5): + minimum_c_version = 12.0 + elif python_version == (3, 4): + minimum_c_version = 12.0 + else: + raise RuntimeError( + 'ozw does not support this version of python' + ) + + environment = Environment( + minimum_c_version, + strict_c_version, + minimum_toolkit_version, + strict_toolkit_version, + minimum_sdk_version, + strict_sdk_version, + minimum_net_version, + strict_net_version, + vs_version + ) + + distutils.log.debug('\n' + str(environment)) + + for key, value in environment.build_environment.items(): + os.environ[key] = value + + return environment + + +if __name__ == '__main__': + distutils.log.set_threshold(distutils.log.DEBUG) + + # build tools 2019 '16.10.31515.178' '16.10.4' + # visual studio 2019 '16.11.31729.503' '16.11.5' + + envr = setup_environment() # vs_version='16.10.4') + print() + print() + print('SET ENVIRONMENT VARIABLES') + print('------------------------------------------------') + print() + for k, v in envr: + if os.pathsep in v: + v = v.split(';') + if not v[-1]: + v = v[:-1] + + print(k + ':', v) + print() diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py new file mode 100644 index 0000000000..54b212c6f3 --- /dev/null +++ b/buildtools/msvc/vswhere.py @@ -0,0 +1,1506 @@ +# ############################################################################# +# +# MIT License +# +# Copyright 2022 Kevin G. Schlosser +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################# + +try: + import comtypes +except ImportError: + raise RuntimeError('the comtypes library is needed to run this script') + +import weakref +import ctypes +from ctypes.wintypes import ( + LPFILETIME, + LPCOLESTR, + ULONG, + LPCWSTR, + LPVOID, + LCID +) + +from comtypes.automation import ( + tagVARIANT +) +from comtypes import ( + GUID, + COMMETHOD, + POINTER, + IUnknown, + HRESULT, + BSTR, +) +from comtypes._safearray import ( # NOQA + SAFEARRAY, + VARIANT_BOOL, + SafeArrayLock, + SafeArrayUnlock +) + +LPVARIANT = POINTER(tagVARIANT) +ENUM = ctypes.c_uint +IID = GUID +CLSID = GUID +MAXUINT = 0xFFFFFFFF +PULONGLONG = POINTER(ctypes.c_ulonglong) +LPSAFEARRAY = POINTER(SAFEARRAY) +_CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree + + +def HRESULT_FROM_WIN32(x): + return x + + +ERROR_FILE_NOT_FOUND = 0x00000002 +ERROR_NOT_FOUND = 0x00000490 + +# Constants +E_NOTFOUND = HRESULT_FROM_WIN32(ERROR_NOT_FOUND) +E_FILENOTFOUND = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) + + +# Enumerations +# The state of an instance. +class InstanceState(ENUM): + # The instance state has not been determined. + eNone = 0 + # The instance installation path exists. + eLocal = 1 + # A product is registered to the instance. + eRegistered = 2 + # No reboot is required for the instance. + eNoRebootRequired = 4 + # do not know what this bit does + eUnknown = 8 + # The instance represents a complete install. + eComplete = MAXUINT + + @property + def value(self): + # noinspection PyUnresolvedReferences + value = ENUM.value.__get__(self) + if value == self.eComplete: + return ['local', 'registered', 'no reboot required'] + if value == self.eNone: + return ['remote', 'unregistered', 'reboot required'] + + res = [] + + if value | self.eLocal == value: + res += ['local'] + else: + res += ['remote'] + if value | self.eRegistered == value: + res += ['registered'] + else: + res += ['unregistered'] + if value | self.eNoRebootRequired == value: + res += ['no reboot required'] + else: + res += ['reboot required'] + + if value | self.eUnknown == value: + res.append('unknown flag set') + + return res + + +eNone = InstanceState.eNone +eLocal = InstanceState.eLocal +eRegistered = InstanceState.eRegistered +eNoRebootRequired = InstanceState.eNoRebootRequired +eUnknown = InstanceState.eUnknown +eComplete = InstanceState.eComplete + + +class Packages(object): + + def __init__(self, packages): + self._packages = packages + + def __iter__(self): + return iter(self._packages) + + def __str__(self): + res = [] + + def _add(items): + for item in items: + res.append('\n'.join( + ' ' + line + for line in str(item).split('\n') + )) + res.append('') + + res.append('vsix:') + _add(self.vsix) + res.append('group:') + _add(self.group) + res.append('component:') + _add(self.component) + res.append('workload:') + _add(self.workload) + res.append('product:') + _add(self.product) + res.append('msi:') + _add(self.msi) + res.append('exe:') + _add(self.exe) + res.append('msu:') + _add(self.msu) + res.append('other:') + _add(self.other) + + return '\n'.join(res) + + @property + def vsix(self): + return [ + package for package in self + if package.type == 'Vsix' + ] + + @property + def group(self): + return [ + package for package in self + if package.type == 'Group' + ] + + @property + def component(self): + return [ + package for package in self + if package.type == 'Component' + ] + + @property + def workload(self): + return [ + package for package in self + if package.type == 'Workload' + ] + + @property + def product(self): + return [ + package for package in self + if package.type == 'Product' + ] + + @property + def msi(self): + return [ + package for package in self + if package.type == 'Msi' + ] + + @property + def exe(self): + return [ + package for package in self + if package.type == 'Exe' + ] + + @property + def msu(self): + return [ + package for package in self + if package.type == 'Msu' + ] + + @property + def other(self): + return [ + package for package in self + if package.type not in ( + 'Exe', 'Msi', 'Product', 'Vsix', + 'Group', 'Component', 'Workload', 'Msu' + ) + ] + + +# Forward declarations + + +IID_ISetupPackageReference = IID("{da8d8a16-b2b6-4487-a2f1-594ccccd6bf5}") + + +# A reference to a package. +class ISetupPackageReference(IUnknown): + _iid_ = IID_ISetupPackageReference + + def __gt__(self, other): + if isinstance(other, ISetupPackageReference): + return self.version > other.version + + other = _convert_version(other) + + if not isinstance(other, tuple): + return False + + return self.version > other + + def __lt__(self, other): + if isinstance(other, ISetupPackageReference): + return self.version < other.version + + other = _convert_version(other) + + if not isinstance(other, str): + return False + + return self.version < other + + def __ge__(self, other): + if isinstance(other, ISetupPackageReference): + return self.version >= other.version + + other = _convert_version(other) + + if not isinstance(other, str): + return False + + return self.version >= other + + def __le__(self, other): + if isinstance(other, ISetupPackageReference): + return self.version <= other.version + + other = _convert_version(other) + + if not isinstance(other, str): + return False + + return self.version <= other + + def __eq__(self, other): + if isinstance(other, ISetupPackageReference): + return self.version == other.version + + other = _convert_version(other) + + if not isinstance(other, str): + return object.__eq__(self, other) + + return self.version == other + + def __ne__(self, other): + return not self.__eq__(other) + + @property + def name(self): + # noinspection PyUnresolvedReferences + return self.GetId() + + @property + def id(self): + # noinspection PyUnresolvedReferences + return self.GetId() + + @property + def version(self): + # noinspection PyUnresolvedReferences + return self.GetVersion() + + @property + def chip(self): + # noinspection PyUnresolvedReferences + return self.GetChip() + + @property + def language(self): + # noinspection PyUnresolvedReferences + return self.GetLanguage() + + @property + def branch(self): + # noinspection PyUnresolvedReferences + return self.GetBranch() + + @property + def type(self): + # noinspection PyUnresolvedReferences + return self.GetType() + + @property + def unique_id(self): + # noinspection PyUnresolvedReferences + return self.GetUniqueId() + + @property + def is_extension(self): + # noinspection PyUnresolvedReferences + return self.GetIsExtension() + + def __str__(self): + res = [ + 'id: ' + str(self.id), + 'version: ' + str(self.version), + 'chip: ' + str(self.chip), + 'language: ' + str(self.language), + 'branch: ' + str(self.branch), + 'type: ' + str(self.type), + 'unique id: ' + str(self.unique_id), + 'is extension: ' + str(self.is_extension) + ] + return '\n'.join(res) + + +IID_ISetupInstance = IID("{B41463C3-8866-43B5-BC33-2B0676F7F42E}") + + +def _convert_version(other): + if isinstance(other, str): + other = tuple(int(item) for item in other.split('.')) + elif isinstance(other, bytes): + other = tuple( + int(item) for item in other.decode('utf-8').split('.') + ) + elif isinstance(other, list): + other = tuple(other) + elif isinstance(other, int): + other = (other,) + elif isinstance(other, float): + other = tuple(int(item) for item in str(other).split('.')) + + if isinstance(other, tuple): + other = '.'.join(str(item) for item in other) + + return other + + +# Information about an instance of a product. +class ISetupInstance(IUnknown): + _iid_ = IID_ISetupInstance + _helper = None + + def __call__(self, helper): + self._helper = helper + return self + + @property + def id(self): + # noinspection PyUnresolvedReferences + return self.GetInstanceId() + + @property + def install_date(self): + # noinspection PyUnresolvedReferences + return self.GetInstallDate() + + @property + def name(self): + # noinspection PyUnresolvedReferences + return self.GetInstallationName() + + @property + def path(self): + # noinspection PyUnresolvedReferences + return self.GetInstallationPath() + + @property + def version(self): + # noinspection PyUnresolvedReferences + return self.GetInstallationVersion() + + @property + def full_version(self): + if self._helper is not None: + return self._helper.ParseVersion(self.version) + + @property + def display_name(self): + try: + # noinspection PyUnresolvedReferences + return self.GetDisplayName() + except OSError: + pass + + @property + def description(self): + try: + # noinspection PyUnresolvedReferences + return self.GetDescription() + except OSError: + pass + + def __str__(self): + res = [ + 'id: ' + str(self.id), + 'name: ' + str(self.name), + 'display name: ' + str(self.display_name), + 'description: ' + str(self.description), + 'path: ' + str(self.path), + 'version: ' + str(self.version), + 'full version: ' + str(self.full_version), + 'install date: ' + str(self.install_date) + ] + return '\n'.join(res) + + +IID_ISetupInstance2 = IID("{89143C9A-05AF-49B0-B717-72E218A2185C}") + + +# Information about an instance of a product. +class ISetupInstance2(ISetupInstance): + _iid_ = IID_ISetupInstance2 + + @property + def packages(self) -> Packages: + # noinspection PyUnresolvedReferences + safearray = self.GetPackages() + + SafeArrayLock(safearray) + + # noinspection PyTypeChecker + packs = comtypes.cast( + safearray.contents.pvData, + POINTER(POINTER(ISetupPackageReference)) + ) + + cPackages = safearray.contents.rgsabound[0].cElements + + res = [] + for i in range(cPackages): + p = packs[i] + res.append(p) + + SafeArrayUnlock(safearray) + res = Packages(res) + return res + + @property + def properties(self): + # noinspection PyUnresolvedReferences + return self.GetProperties() + + @property + def product(self): + """ + version + chip + language + branch + type + unique_id + is_extension + """ + if 'registered' in self.state: + # noinspection PyUnresolvedReferences + return self.GetProduct() + + @property + def state(self): + # noinspection PyUnresolvedReferences + return self.GetState().value + + @property + def product_path(self): + # noinspection PyUnresolvedReferences + return self.GetProductPath() + + @property + def errors(self): + # noinspection PyUnresolvedReferences + errors = self.GetErrors() + try: + return errors.QueryInterface(ISetupErrorState2) + except ValueError: + return errors + + @property + def is_launchable(self): + # noinspection PyUnresolvedReferences + return self.IsLaunchable() + + @property + def is_complete(self): + # noinspection PyUnresolvedReferences + return self.IsComplete() + + @property + def is_prerelease(self): + catalog = self.QueryInterface(ISetupInstanceCatalog) + return catalog.IsPrerelease() + + @property + def catalog(self): + return self.QueryInterface(ISetupInstanceCatalog) + + @property + def engine_path(self): + # noinspection PyUnresolvedReferences + return self.GetEnginePath() + + @property + def localised_properties(self): + return self.QueryInterface(ISetupLocalizedProperties) + + def __str__(self): + res = [ + ISetupInstance.__str__(self), + 'product path: ' + str(self.product_path), + 'is launchable: ' + str(self.is_launchable), + 'is complete: ' + str(self.is_complete), + 'is prerelease: ' + str(self.is_prerelease), + 'state: ' + str(self.state), + 'engine path: ' + str(self.engine_path), + 'errors:', + '{errors}', + 'product: ', + '{product}', + 'packages:', + '{packages}', + 'properties:', + '{properties}', + 'catalog:', + '{catalog}' + ] + + res = '\n'.join(res) + + return res.format( + errors='\n'.join( + ' ' + line + for line in str(self.errors).split('\n') + ), + product='\n'.join( + ' ' + line + for line in str(self.product).split('\n') + ), + packages='\n'.join( + ' ' + line + for line in str(self.packages).split('\n') + ), + properties='\n'.join( + ' ' + line + for line in str(self.properties).split('\n') + ), + catalog='\n'.join( + ' ' + line + for line in str(self.catalog).split('\n') + ) + ) + + +IID_ISetupInstanceCatalog = IID("{9AD8E40F-39A2-40F1-BF64-0A6C50DD9EEB}") + + +# Information about a catalog used to install an instance. +class ISetupInstanceCatalog(IUnknown): + _iid_ = IID_ISetupInstanceCatalog + + @property + def id(self): + for prop in self: + if prop.name == 'id': + return prop.value + + @property + def build_branch(self): + for prop in self: + if prop.name == 'buildBranch': + return prop.value + + @property + def build_version(self): + for prop in self: + if prop.name == 'buildVersion': + return prop.value + + @property + def local_build(self): + for prop in self: + if prop.name == 'localBuild': + return prop.value + + @property + def manifest_name(self): + for prop in self: + if prop.name == 'manifestName': + return prop.value + + @property + def manifest_type(self): + for prop in self: + if prop.name == 'manifestType': + return prop.value + + @property + def product_display_version(self): + for prop in self: + if prop.name == 'productDisplayVersion': + return prop.value + + @property + def product_line(self): + for prop in self: + if prop.name == 'productLine': + return prop.value + + @property + def product_line_version(self): + for prop in self: + if prop.name == 'productLineVersion': + return prop.value + + @property + def product_milestone(self): + for prop in self: + if prop.name == 'productMilestone': + return prop.value + + @property + def product_milestone_is_prerelease(self): + for prop in self: + if prop.name == 'productMilestoneIsPreRelease': + return prop.value + + @property + def product_name(self): + for prop in self: + if prop.name == 'productName': + return prop.value + + @property + def product_patch_version(self): + for prop in self: + if prop.name == 'productPatchVersion': + return prop.value + + @property + def product_prerelease_milestone_suffix(self): + for prop in self: + if prop.name == 'productPreReleaseMilestoneSuffix': + return prop.value + + @property + def product_semantic_version(self): + for prop in self: + if prop.name == 'productSemanticVersion': + return prop.value + + def __iter__(self): + # noinspection PyUnresolvedReferences + for prop in self.GetCatalogInfo(): + yield prop + + def __str__(self): + res = [prop.name + ': ' + str(prop.value) for prop in self] + return '\n'.join(res) + + +IID_ISetupLocalizedProperties = IID("{F4BD7382-FE27-4AB4-B974-9905B2A148B0}") + + +# Provides localized properties of an instance of a product. +class ISetupLocalizedProperties(IUnknown): + _iid_ = IID_ISetupLocalizedProperties + + +IID_IEnumSetupInstances = IID("{6380BCFF-41D3-4B2E-8B2E-BF8A6810C848}") + + +# A enumerator of installed ISetupInstance objects. +class IEnumSetupInstances(IUnknown): + _iid_ = IID_IEnumSetupInstances + + def __iter__(self): + while True: + try: + # noinspection PyUnresolvedReferences + set_instance, num = self.Next(1) + yield set_instance + + except comtypes.COMError: + break + + +IID_ISetupConfiguration = IID("{42843719-DB4C-46C2-8E7C-64F1816EFD5B}") + + +# Gets information about product instances set up on the machine. +class ISetupConfiguration(IUnknown): + _iid_ = IID_ISetupConfiguration + + def __call__(self): + try: + return self.QueryInterface(ISetupConfiguration2) + except ValueError: + return self + + def __iter__(self): + # noinspection PyUnresolvedReferences + setup_enum = self.EnumInstances() + helper = self.QueryInterface(ISetupHelper) + + for si in setup_enum: + if not si: + break + + yield si(helper) + + +IID_ISetupConfiguration2 = IID("{26AAB78C-4A60-49D6-AF3B-3C35BC93365D}") + + +# Gets information about product instances. +class ISetupConfiguration2(ISetupConfiguration): + _iid_ = IID_ISetupConfiguration2 + + def __iter__(self): + # noinspection PyUnresolvedReferences + setup_enum = self.EnumAllInstances() + helper = self.QueryInterface(ISetupHelper) + + for si in setup_enum: + if not si: + break + + yield si.QueryInterface(ISetupInstance2)(helper) + + +IID_ISetupHelper = IID("{42b21b78-6192-463e-87bf-d577838f1d5c}") + + +class ISetupHelper(IUnknown): + _iid_ = IID_ISetupHelper + + +IID_ISetupErrorState = IID("{46DCCD94-A287-476A-851E-DFBC2FFDBC20}") + + +# Information about the error state of an instance. +class ISetupErrorState(IUnknown): + _iid_ = IID_ISetupErrorState + + @property + def failed_packages(self) -> Packages: + try: + # noinspection PyUnresolvedReferences + safearray = self.GetFailedPackages() + except ValueError: + return Packages([]) + + SafeArrayLock(safearray) + + # noinspection PyTypeChecker + packs = comtypes.cast( + safearray.contents.pvData, + POINTER(POINTER(ISetupFailedPackageReference)) + ) + + cPackages = safearray.contents.rgsabound[0].cElements + + res = [] + for i in range(cPackages): + p = packs[i] + p = p.QueryInterface(ISetupFailedPackageReference2) + res.append(p) + + SafeArrayUnlock(safearray) + res = Packages(res) + return res + + @property + def skipped_packages(self) -> Packages: + try: + # noinspection PyUnresolvedReferences + safearray = self.GetSkippedPackages() + except ValueError: + return Packages([]) + + SafeArrayLock(safearray) + + # noinspection PyTypeChecker + packs = comtypes.cast( + safearray.contents.pvData, + POINTER(POINTER(ISetupFailedPackageReference)) + ) + + cPackages = safearray.contents.rgsabound[0].cElements + + res = [] + for i in range(cPackages): + p = packs[i] + p = p.QueryInterface(ISetupFailedPackageReference2) + res.append(p) + + SafeArrayUnlock(safearray) + res = Packages(res) + return res + + def __str__(self): + res = ['failed packages: '] + res.extend([ + ' ' + line for line in + str(self.failed_packages).split('\n') + ]) + + res += ['skipped packages: '] + res.extend([ + ' ' + line for line in + str(self.skipped_packages).split('\n') + ]) + + return '\n'.join(res) + + +IID_ISetupErrorState2 = IID("{9871385B-CA69-48F2-BC1F-7A37CBF0B1EF}") + + +# Information about the error state of an instance. +class ISetupErrorState2(ISetupErrorState): + _iid_ = IID_ISetupErrorState2 + + @property + def error_log_file_path(self): + # noinspection PyUnresolvedReferences + return self.GetErrorLogFilePath() + + @property + def log_file_path(self): + # noinspection PyUnresolvedReferences + return self.GetLogFilePath() + + def __str__(self): + res = [ + 'error log file path: ' + self.error_log_file_path, + 'log file path: ' + self.log_file_path, + ISetupErrorState.__str__(self) + ] + return '\n'.join(res) + + +IID_ISetupFailedPackageReference = IID( + "{E73559CD-7003-4022-B134-27DC650B280F}" + ) + + +# A reference to a failed package. +class ISetupFailedPackageReference(ISetupPackageReference): + _iid_ = IID_ISetupFailedPackageReference + + +IID_ISetupFailedPackageReference2 = IID( + "{0FAD873E-E874-42E3-B268-4FE2F096B9CA}" + ) + + +# A reference to a failed package. +class ISetupFailedPackageReference2(ISetupFailedPackageReference): + _iid_ = IID_ISetupFailedPackageReference2 + + +IID_ISetupPropertyStore = IID("{C601C175-A3BE-44BC-91F6-4568D230FC83}") + + +class Property(object): + + def __init__(self, name, value): + self._name = name + self._value = value + + @property + def name(self): + return self._name + + @property + def value(self): + return self._value + + def __str__(self): + return self.name + ': ' + str(self.value) + + +# Provides named properties. +class ISetupPropertyStore(IUnknown): + _iid_ = IID_ISetupPropertyStore + + @property + def names(self): + # noinspection PyUnresolvedReferences + safearray = self.GetNames() + + SafeArrayLock(safearray) + + names = comtypes.cast(safearray.contents.pvData, POINTER(BSTR)) + cPackages = safearray.contents.rgsabound[0].cElements + + res = [] + for i in range(cPackages): + res.append(str(names[i])) + + SafeArrayUnlock(safearray) + + return res + + def __iter__(self): + for n in self.names: + # noinspection PyUnresolvedReferences + yield Property(n, self.GetValue(n)) + + def __str__(self): + return '\n'.join(str(prop) for prop in self) + + +IID_ISetupLocalizedPropertyStore = IID( + "{5BB53126-E0D5-43DF-80F1-6B161E5C6F6C}" + ) + + +# Provides localized named properties. +class ISetupLocalizedPropertyStore(IUnknown): + _iid_ = IID_ISetupLocalizedPropertyStore + + @property + def names(self): + # noinspection PyUnresolvedReferences + safearray = self.GetNames() + + SafeArrayLock(safearray) + + names = comtypes.cast(safearray.contents.pvData, POINTER(BSTR)) + cPackages = safearray.contents.rgsabound[0].cElements + + res = [] + for i in range(cPackages): + res.append(str(names[i])) + + SafeArrayUnlock(safearray) + + return res + + def __iter__(self): + for n in self.names: + # noinspection PyUnresolvedReferences + yield Property(n, self.GetValue(n)) + + def __str__(self): + return '\n'.join(str(prop) for prop in self) + + +ISetupPackageReference._methods_ = [ + # Gets the general package identifier. + COMMETHOD( + [], + HRESULT, + "GetId", + (['out'], POINTER(BSTR), "pbstrId") + ), + # Gets the version of the package. + COMMETHOD( + [], + HRESULT, + "GetVersion", + (['out'], POINTER(BSTR), "pbstrVersion") + + ), + # Gets the target process architecture of the package. + COMMETHOD( + [], + HRESULT, + "GetChip", + (['out'], POINTER(BSTR), "pbstrChip") + ), + # Gets the language and optional region identifier. + COMMETHOD( + [], + HRESULT, + "GetLanguage", + (['out'], POINTER(BSTR), "pbstrLanguage") + ), + # Gets the build branch of the package. + COMMETHOD( + [], + HRESULT, + "GetBranch", + (['out'], POINTER(BSTR), "pbstrBranch") + ), + # Gets the type of the package. + COMMETHOD( + [], + HRESULT, + "GetType", + (['out'], POINTER(BSTR), "pbstrType") + ), + # Gets the unique identifier consisting of all defined tokens. + COMMETHOD( + [], + HRESULT, + "GetUniqueId", + (['out'], POINTER(BSTR), "pbstrUniqueId") + ), + # Gets a value indicating whether the package refers to + # an external extension. + COMMETHOD( + [], + HRESULT, + "GetIsExtension", + (['out'], POINTER(VARIANT_BOOL), "pfIsExtension") + ) +] + +ISetupInstance._methods_ = [ + # Gets the instance identifier (should match the name of the + # parent instance directory). + COMMETHOD( + [], + HRESULT, + "GetInstanceId", + (['out'], POINTER(BSTR), "pbstrInstanceId") + ), + # Gets the local date and time when the installation + # was originally installed. + COMMETHOD( + [], + HRESULT, + "GetInstallDate", + (['out'], LPFILETIME, "pInstallDate") + ), + # Gets the unique name of the installation, often + # indicating the branch and other information used for telemetry. + COMMETHOD( + [], + HRESULT, + "GetInstallationName", + (['out'], POINTER(BSTR), "pbstrInstallationName") + ), + # Gets the path to the installation root of the product. + COMMETHOD( + [], + HRESULT, + "GetInstallationPath", + (['out'], POINTER(BSTR), "pbstrInstallationPath") + ), + # Gets the version of the product installed in this instance. + COMMETHOD( + [], + HRESULT, + "GetInstallationVersion", + (['out'], POINTER(BSTR), "pbstrInstallationVersion") + ), + # Gets the display name (title) of the product installed + # in this instance. + COMMETHOD( + [], + HRESULT, + "GetDisplayName", + (['out'], POINTER(BSTR), "pbstrDisplayName") + ), + # Gets the description of the product installed in this instance. + COMMETHOD( + [], + HRESULT, + "GetDescription", + # (['in'], LCID, "lcid"), + (['out'], POINTER(BSTR), "pbstrDescription") + ), + # Resolves the optional relative path to the root path of the instance. + COMMETHOD( + [], + HRESULT, + "ResolvePath", + (['in'], LPCOLESTR, "pwszRelativePath"), + (['out'], POINTER(BSTR), "pbstrAbsolutePath") + + ) +] + +# noinspection PyTypeChecker +ISetupInstance2._methods_ = [ + # Gets the state of the instance. + COMMETHOD( + [], + HRESULT, + "GetState", + (['out'], POINTER(InstanceState), "pState") + ), + # Gets an array of package references registered to the instance. + COMMETHOD( + [], + HRESULT, + "GetPackages", + (['out'], POINTER(LPSAFEARRAY), "ppsaPackages") + ), + # Gets a pointer to the ISetupPackageReference that represents + # the registered product. + COMMETHOD( + [], + HRESULT, + "GetProduct", + (['out'], POINTER(POINTER(ISetupPackageReference)), "ppPackage") + ), + # Gets the relative path to the product application, if available. + COMMETHOD( + [], + HRESULT, + "GetProductPath", + (['out'], POINTER(BSTR), "pbstrProductPath") + ), + # Gets the error state of the instance, if available. + COMMETHOD( + [], + HRESULT, + "GetErrors", + (['out'], POINTER(POINTER(ISetupErrorState)), "ppErrorState") + ), + # Gets a value indicating whether the instance can be launched. + COMMETHOD( + [], + HRESULT, + "IsLaunchable", + (['out'], POINTER(VARIANT_BOOL), "pfIsLaunchable") + ), + # Gets a value indicating whether the instance is complete. + COMMETHOD( + [], + HRESULT, + "IsComplete", + (['out'], POINTER(VARIANT_BOOL), "pfIsComplete") + ), + # Gets product-specific properties. + COMMETHOD( + [], + HRESULT, + "GetProperties", + (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppProperties") + ), + # Gets the directory path to the setup engine + # that installed the instance. + COMMETHOD( + [], + HRESULT, + "GetEnginePath", + (['out'], POINTER(BSTR), "pbstrEnginePath") + ) +] + +# noinspection PyTypeChecker +ISetupInstanceCatalog._methods_ = [ + # Gets catalog information properties. + COMMETHOD( + [], + HRESULT, + "GetCatalogInfo", + (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppCatalogInfo") + ), + # Gets a value indicating whether the catalog is a prerelease. + COMMETHOD( + [], + HRESULT, + "IsPrerelease", + (['out'], POINTER(VARIANT_BOOL), "pfIsPrerelease") + ) +] + +# noinspection PyTypeChecker +ISetupLocalizedProperties._methods_ = [ + # Gets localized product-specific properties. + COMMETHOD( + [], + HRESULT, + "GetLocalizedProperties", + ( + ['out'], + POINTER(POINTER(ISetupLocalizedPropertyStore)), + "ppLocalizedProperties" + ) + ), + # Gets localized channel-specific properties. + COMMETHOD( + [], + HRESULT, + "GetLocalizedChannelProperties", + ( + ['out'], + POINTER(POINTER(ISetupLocalizedPropertyStore)), + "ppLocalizedChannelProperties" + ) + ) +] + +# noinspection PyTypeChecker +IEnumSetupInstances._methods_ = [ + # Retrieves the next set of product instances in the + # enumeration sequence. + COMMETHOD( + [], + HRESULT, + "Next", + (['in'], ULONG, "celt"), + (['out'], POINTER(POINTER(ISetupInstance)), "rgelt"), + (['out'], POINTER(ULONG), "pceltFetched") + ), + # Skips the next set of product instances in the enumeration sequence. + COMMETHOD( + [], + HRESULT, + "Skip", + (['in'], ULONG, "celt") + ), + # Resets the enumeration sequence to the beginning. + COMMETHOD( + [], + HRESULT, + "Reset" + ), + # Creates a new enumeration object in the same state as the current + # enumeration object: the new object points to the same place in the + # enumeration sequence. + COMMETHOD( + [], + HRESULT, + "Clone", + (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppenum") + ) +] + +# noinspection PyTypeChecker +ISetupConfiguration._methods_ = [ + # Enumerates all completed product instances installed. + COMMETHOD( + [], + HRESULT, + "EnumInstances", + (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") + ), + # Gets the instance for the current process path. + COMMETHOD( + [], + HRESULT, + "GetInstanceForCurrentProcess", + (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") + ), + # Gets the instance for the given path. + COMMETHOD( + [], + HRESULT, + "GetInstanceForPath", + (['in'], LPCWSTR, "wzPath"), + (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") + ) +] + +# noinspection PyTypeChecker +ISetupConfiguration2._methods_ = [ + # Enumerates all product instances. + COMMETHOD( + [], + HRESULT, + "EnumAllInstances", + (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") + ) +] + +ISetupHelper._methods_ = [ + # Parses a dotted quad version string into a 64-bit unsigned integer. + COMMETHOD( + [], + HRESULT, + "ParseVersion", + (['in'], LPCOLESTR, "pwszVersion"), + (['out'], PULONGLONG, "pullVersion") + ), + # Parses a dotted quad version string into a 64-bit unsigned integer. + COMMETHOD( + [], + HRESULT, + "ParseVersionRange", + (['in'], LPCOLESTR, "pwszVersionRange"), + (['out'], PULONGLONG, "pullMinVersion"), + (['out'], PULONGLONG, "pullMaxVersion") + ) +] + +ISetupErrorState._methods_ = ( + # Gets an array of failed package references. + COMMETHOD( + [], + HRESULT, + "GetFailedPackages", + (['out'], POINTER(LPSAFEARRAY), "ppsaFailedPackages") + ), + # Gets an array of skipped package references. + COMMETHOD( + [], + HRESULT, + "GetSkippedPackages", + (['out'], POINTER(LPSAFEARRAY), "ppsaSkippedPackages") + ) +) + +ISetupErrorState2._methods_ = ( + # Gets the path to the error log. + COMMETHOD( + [], + HRESULT, + "GetErrorLogFilePath", + (['out'], POINTER(BSTR), "pbstrErrorLogFilePath") + ), + # Gets the path to the main setup log. + COMMETHOD( + [], + HRESULT, + "GetLogFilePath", + (['out'], POINTER(BSTR), "pbstrLogFilePath") + ) +) + +ISetupFailedPackageReference._methods_ = () + +ISetupFailedPackageReference2._methods_ = ( + # Gets the path to the optional package log. + COMMETHOD( + [], + HRESULT, + "GetLogFilePath", + (['out'], POINTER(BSTR), "pbstrLogFilePath") + ), + # Gets the description of the package failure. + COMMETHOD( + [], + HRESULT, + "GetDescription", + (['out'], POINTER(BSTR), "pbstrDescription") + ), + # Gets the signature to use for feedback reporting. + COMMETHOD( + [], + HRESULT, + "GetSignature", + (['out'], POINTER(BSTR), "pbstrSignature") + ), + # Gets the array of details for this package failure. + COMMETHOD( + [], + HRESULT, + "GetDetails", + (['out'], POINTER(LPSAFEARRAY), "ppsaDetails") + ), + # Gets an array of packages affected by this package failure. + COMMETHOD( + [], + HRESULT, + "GetAffectedPackages", + (['out'], POINTER(LPSAFEARRAY), "ppsaAffectedPackages") + ) +) + +ISetupPropertyStore._methods_ = ( + # Gets an array of property names in this property store. + COMMETHOD( + [], + HRESULT, + "GetNames", + (['out'], POINTER(LPSAFEARRAY), "ppsaNames") + ), + # Gets the value of a named property in this property store. + COMMETHOD( + [], + HRESULT, + "GetValue", + (['in'], LPCOLESTR, "pwszName"), + (['out'], LPVARIANT, "pvtValue") + ) +) + +ISetupLocalizedPropertyStore._methods_ = ( + # Gets an array of property names in this property store. + COMMETHOD( + [], + HRESULT, + "GetNames", + (['in'], LCID, "lcid"), + (['out'], POINTER(LPSAFEARRAY), "ppsaNames") + ), + # Gets the value of a named property in this property store. + COMMETHOD( + [], + HRESULT, + "GetValue", + (['in'], LPCOLESTR, "pwszName"), + (['in'], LCID, "lcid"), + (['out'], LPVARIANT, "pvtValue") + ) +) + +CLSID_SetupConfiguration = CLSID("{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}") + + +# This class implements ISetupConfiguration, ISetupConfiguration2 and +# ISetupHelper. +class SetupConfiguration(IUnknown): + _instance_ = None + _iid_ = CLSID_SetupConfiguration + + ISetupConfiguration = ISetupConfiguration + ISetupConfiguration2 = ISetupConfiguration2 + ISetupHelper = ISetupHelper + + # Gets an ISetupConfiguration that provides information about + # product instances installed on the machine. + + # noinspection PyTypeChecker + _methods_ = [ + COMMETHOD( + [], + HRESULT, + "GetSetupConfiguration", + ( + ['out'], + POINTER(POINTER(ISetupConfiguration)), + "ppConfiguration" + ), + ([], LPVOID, "pReserved") + ) + ] + + @classmethod + def _callback(cls, _): + cls._instance_ = None + comtypes.CoUninitialize() + + @classmethod + def GetSetupConfiguration(cls): + if cls._instance_ is None: + comtypes.CoInitialize() + instance = comtypes.CoCreateInstance( + CLSID_SetupConfiguration, + ISetupConfiguration, + comtypes.CLSCTX_ALL + )() + + cls._instance_ = weakref.ref(instance, cls._callback) + else: + instance = cls._instance_() + + return instance + + +if __name__ == '__main__': + setup_config = SetupConfiguration.GetSetupConfiguration() + for instance_config in setup_config: + print(instance_config) From d4041c3d3bfafff7084a63b32fee09e54b370762 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 17 Jan 2022 05:53:16 -0700 Subject: [PATCH 02/23] Adds additional bash.exe source. The way the current build system worked it limited the build to only being able to be performed on a Windows 10+ system when compiling on Windows. This would require a user to have the Unix subsystem feature that is apart of Windows installed. Most users that are going to build wxPython will have Git installed and Git comes with bash.exe so why not use it? --- build.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/build.py b/build.py index 1d21c8587b..7b311778a8 100755 --- a/build.py +++ b/build.py @@ -884,6 +884,31 @@ def getWafBuildBase(): def getBashPath(): """Check if there is a bash.exe on the PATH""" bash = which('bash.exe') + + if not bash and isWindows: + import winreg + + try: + handle = winreg.OpenKeyEx( + winreg.HKEY_LOCAL_MACHINE, + r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1' + ) + except winreg.error: + pass + else: + try: + path = winreg.QueryValueEx(handle, "InstallLocation")[0] + winreg.CloseKey(handle) + path = os.path.join(path, 'bin', 'bash.exe') + + if os.path.exists(path): + bash = path + + except winreg.error: + pass + + winreg.CloseKey(handle) + return bash From 16d65de5d89168fe375520bdf7860e1c058d816a Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 00:24:16 -0700 Subject: [PATCH 03/23] Adds dependency link for comtypes. --- build.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.py b/build.py index 7b311778a8..c52ea3ebcf 100755 --- a/build.py +++ b/build.py @@ -804,10 +804,10 @@ def checkCompiler(quiet=False): name='msvc_compiler', script_args=['build'], setup_requires=['comtypes'], - # dependency_links=[ - # 'https://github.com/enthought/comtypes' - # '/tarball/1.1.10.tar.gz#egg=comtypes' - # ] + dependency_links=[ + 'https://github.com/kdschlosser/comtypes/python_3' + '/tarball/1.1.10.tar.gz#egg=comtypes' + ] ) from buildtools import msvc From fb47e01a5367efbb6b7fe719c170f803dcdc8720 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 00:51:53 -0700 Subject: [PATCH 04/23] Fixes issue with BSTR not being the proper size on x86 --- buildtools/msvc/vswhere.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py index 54b212c6f3..0179d743a2 100644 --- a/buildtools/msvc/vswhere.py +++ b/buildtools/msvc/vswhere.py @@ -48,8 +48,7 @@ COMMETHOD, POINTER, IUnknown, - HRESULT, - BSTR, + HRESULT ) from comtypes._safearray import ( # NOQA SAFEARRAY, @@ -68,6 +67,35 @@ _CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree +class BSTR(ctypes.c_wchar_p): + """The windows BSTR data type""" + _needsfree = False + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.value) + + def __ctypes_from_outparam__(self): + self._needsfree = True + return self.value + + def __del__(self, _free=ctypes.windll.oleaut32.SysFreeString): + # Free the string if self owns the memory + # or if instructed by __ctypes_from_outparam__. + if self._b_base_ is None or self._needsfree: + _free(self) + + def from_param(cls, value): + """Convert into a foreign function call parameter.""" + if isinstance(value, cls): + return value + # Although the builtin SimpleCData.from_param call does the + # right thing, it doesn't ensure that SysFreeString is called + # on destruction. + return cls(value) + + from_param = classmethod(from_param) + + def HRESULT_FROM_WIN32(x): return x From 6151ae0b4c0d94b406b6f6361cffdf9c1ba34496 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 01:36:51 -0700 Subject: [PATCH 05/23] moves comtypes install to devel.txt --- build.py | 25 ------------------------- requirements/devel.txt | 2 ++ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/build.py b/build.py index c52ea3ebcf..586a317227 100755 --- a/build.py +++ b/build.py @@ -785,31 +785,6 @@ def uploadTree(srcPath, destPath, options, days=30): def checkCompiler(quiet=False): if isWindows: - import setuptools - - # we use the internal mechanics of setup tools to collect comtypes - # which is a library used to build the COM interfaces to Visual Studio - # In order for those mechanics to work properly the wheel library needs - # to be available in the python installation and it cannot be added - # during runtime. This is because of how setuptools turns the collected - # library into an egg. - try: - __import__('wheel') - except ImportError: - raise RuntimeError( - 'The "wheel" library is required to build wxPython for Windows' - ) - - setuptools.setup( - name='msvc_compiler', - script_args=['build'], - setup_requires=['comtypes'], - dependency_links=[ - 'https://github.com/kdschlosser/comtypes/python_3' - '/tarball/1.1.10.tar.gz#egg=comtypes' - ] - ) - from buildtools import msvc # setup the build environment making the minimum conpiler version 14.2 diff --git a/requirements/devel.txt b/requirements/devel.txt index 18164bdcc9..3f2ce202ec 100644 --- a/requirements/devel.txt +++ b/requirements/devel.txt @@ -19,3 +19,5 @@ sphinx==2.2.0 ; python_version >= '3.0' sphinx==1.8.5 ; python_version < '3.0' doc2dash==2.3.0 beautifulsoup4 + +https://github.com/kdschlosser/comtypes/tarball/python_3; sys_platform == 'win32' From 72bd325a5ff03aec03e9e62a1bb2102de15b147c Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 02:24:42 -0700 Subject: [PATCH 06/23] Trying to solve issue with BSTR --- buildtools/msvc/vswhere.py | 59 ++++++++++++++++---------------------- requirements/devel.txt | 2 +- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py index 0179d743a2..1dd12ffead 100644 --- a/buildtools/msvc/vswhere.py +++ b/buildtools/msvc/vswhere.py @@ -41,7 +41,8 @@ ) from comtypes.automation import ( - tagVARIANT + tagVARIANT, + BSTR ) from comtypes import ( GUID, @@ -58,6 +59,7 @@ ) LPVARIANT = POINTER(tagVARIANT) +VARIANT = tagVARIANT ENUM = ctypes.c_uint IID = GUID CLSID = GUID @@ -67,35 +69,6 @@ _CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree -class BSTR(ctypes.c_wchar_p): - """The windows BSTR data type""" - _needsfree = False - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.value) - - def __ctypes_from_outparam__(self): - self._needsfree = True - return self.value - - def __del__(self, _free=ctypes.windll.oleaut32.SysFreeString): - # Free the string if self owns the memory - # or if instructed by __ctypes_from_outparam__. - if self._b_base_ is None or self._needsfree: - _free(self) - - def from_param(cls, value): - """Convert into a foreign function call parameter.""" - if isinstance(value, cls): - return value - # Although the builtin SimpleCData.from_param call does the - # right thing, it doesn't ensure that SysFreeString is called - # on destruction. - return cls(value) - - from_param = classmethod(from_param) - - def HRESULT_FROM_WIN32(x): return x @@ -979,7 +952,7 @@ def names(self): res = [] for i in range(cPackages): - res.append(str(names[i])) + res.append(names[i]) SafeArrayUnlock(safearray) @@ -988,7 +961,15 @@ def names(self): def __iter__(self): for n in self.names: # noinspection PyUnresolvedReferences - yield Property(n, self.GetValue(n)) + v = VARIANT() + + self.GetValue(n, ctypes.byref(v)) + + v = v.value + if isinstance(v, BSTR): + v = v.value + + yield Property(n.value, v) def __str__(self): return '\n'.join(str(prop) for prop in self) @@ -1015,7 +996,7 @@ def names(self): res = [] for i in range(cPackages): - res.append(str(names[i])) + res.append(names[i]) SafeArrayUnlock(safearray) @@ -1024,7 +1005,15 @@ def names(self): def __iter__(self): for n in self.names: # noinspection PyUnresolvedReferences - yield Property(n, self.GetValue(n)) + v = VARIANT() + + self.GetValue(n, ctypes.byref(v)) + + v = v.value + if isinstance(v, BSTR): + v = v.value + + yield Property(n.value, v) def __str__(self): return '\n'.join(str(prop) for prop in self) @@ -1451,7 +1440,7 @@ def __str__(self): HRESULT, "GetValue", (['in'], LPCOLESTR, "pwszName"), - (['out'], LPVARIANT, "pvtValue") + (['in'], LPVARIANT, "pvtValue") ) ) diff --git a/requirements/devel.txt b/requirements/devel.txt index 3f2ce202ec..2b8c2edfa6 100644 --- a/requirements/devel.txt +++ b/requirements/devel.txt @@ -20,4 +20,4 @@ sphinx==1.8.5 ; python_version < '3.0' doc2dash==2.3.0 beautifulsoup4 -https://github.com/kdschlosser/comtypes/tarball/python_3; sys_platform == 'win32' +https://github.com/kdschlosser/comtypes/tarball/bstr_type; sys_platform == 'win32' From 1933e23a330d067a3712e33d7fd9e77d9b0b1536 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 02:41:06 -0700 Subject: [PATCH 07/23] fixes exception catching for GetDisplayName --- buildtools/msvc/vswhere.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py index 1dd12ffead..4f879fb8fa 100644 --- a/buildtools/msvc/vswhere.py +++ b/buildtools/msvc/vswhere.py @@ -436,7 +436,7 @@ def display_name(self): try: # noinspection PyUnresolvedReferences return self.GetDisplayName() - except OSError: + except (OSError, ValueError): pass @property @@ -444,7 +444,7 @@ def description(self): try: # noinspection PyUnresolvedReferences return self.GetDescription() - except OSError: + except (OSError, ValueError): pass def __str__(self): From 4fa55bb028cd030f4a7d65a196c3a0b06e52b465 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 18 Jan 2022 15:00:38 -0700 Subject: [PATCH 08/23] fixes GetDescription and GetDisplayName --- buildtools/msvc/__init__.py | 14 ++++++++++---- buildtools/msvc/vswhere.py | 16 +++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index dc4a99f7ef..e727b972b9 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -2064,6 +2064,7 @@ def __str__(self): template = ( '== {name}==============================================\n' + ' description: {description}\n' ' version: {version}\n' ' version (friendly): {product_line_version}\n' ' display version: {product_display_version}\n' @@ -2075,11 +2076,15 @@ def __str__(self): ' state: {state}\n' ) - if self.__name__ == 'VisualStudioInfo': - name = 'Visual Studio ' - else: - name = 'Build Tools ==' + name = installation.display_name + + if name is None: + if self.__name__ == 'VisualStudioInfo': + name = 'Visual Studio ' + else: + name = 'Build Tools ==' + description = installation.description path = installation.path version = installation.version is_complete = installation.is_complete @@ -2094,6 +2099,7 @@ def __str__(self): return template.format( name=name, + description=description, path=path, version=version, is_complete=is_complete, diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py index 4f879fb8fa..bd025246a2 100644 --- a/buildtools/msvc/vswhere.py +++ b/buildtools/msvc/vswhere.py @@ -68,6 +68,11 @@ LPSAFEARRAY = POINTER(SAFEARRAY) _CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree +kernel32 = ctypes.windll.kernel32 + +_GetUserDefaultLCID = kernel32.GetUserDefaultLCID +_GetUserDefaultLCID.restype = LCID + def HRESULT_FROM_WIN32(x): return x @@ -435,16 +440,16 @@ def full_version(self): def display_name(self): try: # noinspection PyUnresolvedReferences - return self.GetDisplayName() - except (OSError, ValueError): + return self.GetDisplayName(_GetUserDefaultLCID()) + except (OSError, ValueError, comtypes.COMError): pass @property def description(self): try: # noinspection PyUnresolvedReferences - return self.GetDescription() - except (OSError, ValueError): + return self.GetDescription(_GetUserDefaultLCID()) + except (OSError, ValueError, comtypes.COMError): pass def __str__(self): @@ -1125,6 +1130,7 @@ def __str__(self): [], HRESULT, "GetDisplayName", + (['in'], LCID, "lcid"), (['out'], POINTER(BSTR), "pbstrDisplayName") ), # Gets the description of the product installed in this instance. @@ -1132,7 +1138,7 @@ def __str__(self): [], HRESULT, "GetDescription", - # (['in'], LCID, "lcid"), + (['in'], LCID, "lcid"), (['out'], POINTER(BSTR), "pbstrDescription") ), # Resolves the optional relative path to the root path of the instance. From 087c2d49e1f4e668198b92ec436242c63a0df52e Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 25 Jan 2022 07:31:52 -0700 Subject: [PATCH 09/23] Removes dependence on comtypes --- buildtools/msvc/__init__.py | 482 ++++++++++--------- buildtools/msvc/vswhere.py | 895 +++++++++++++++++++++++++++++++++--- requirements/devel.txt | 2 - 3 files changed, 1083 insertions(+), 296 deletions(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index e727b972b9..5a8319111c 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -346,187 +346,6 @@ def _convert_version(ver): return ver -class Environment(object): - - def __init__( - self, - minimum_c_version: Optional[Union[int, float]] = None, - strict_c_version: Optional[Union[int, float]] = None, - minimum_toolkit_version: Optional[int] = None, - strict_toolkit_version: Optional[int] = None, - minimum_sdk_version: Optional[str] = None, - strict_sdk_version: Optional[str] = None, - minimum_net_version: Optional[str] = None, - strict_net_version: Optional[str] = None, - vs_version: Optional[Union[str, int]] = None - ): - self.python = PythonInfo() - - self.visual_c = VisualCInfo( - self, - minimum_c_version, - strict_c_version, - minimum_toolkit_version, - strict_toolkit_version, - vs_version - ) - - self.visual_studio = VisualStudioInfo( - self, - self.visual_c - ) - - self.windows_sdk = WindowsSDKInfo( - self, - self.visual_c, - minimum_sdk_version, - strict_sdk_version - ) - - self.dot_net = NETInfo( - self, - self.visual_c, - self.windows_sdk.version, - minimum_net_version, - strict_net_version - ) - - @property - def machine_architecture(self): - import platform - return 'x64' if '64' in platform.machine() else 'x86' - - @property - def platform(self): - """ - :return: x86 or x64 - """ - import platform - - win_64 = self.machine_architecture == 'x64' - python_64 = platform.architecture()[0] == '64bit' and win_64 - - return 'x64' if python_64 else 'x86' - - @property - def configuration(self): - """ - Build configuration - :return: one of ReleaseDLL, DebugDLL - """ - - if os.path.splitext(sys.executable)[0].endswith('_d'): - config = 'Debug' - else: - config = 'Release' - - return config - - def __iter__(self): - for item in self.build_environment.items(): - yield item - - @property - def build_environment(self): - """ - This would be the work horse. This is where all of the gathered - information is put into a single container and returned. - The information is then added to os.environ in order to allow the - build process to run properly. - - List of environment variables generated: - PATH - LIBPATH - LIB - INCLUDE - Platform - FrameworkDir - FrameworkVersion - FrameworkDIR32 - FrameworkVersion32 - FrameworkDIR64 - FrameworkVersion64 - VCToolsRedistDir - VCINSTALLDIR - VCToolsInstallDir - VCToolsVersion - WindowsLibPath - WindowsSdkDir - WindowsSDKVersion - WindowsSdkBinPath - WindowsSdkVerBinPath - WindowsSDKLibVersion - __DOTNET_ADD_32BIT - __DOTNET_ADD_64BIT - __DOTNET_PREFERRED_BITNESS - Framework{framework version}Version - NETFXSDKDir - UniversalCRTSdkDir - UCRTVersion - ExtensionSdkDir - - These last 2 are set to ensure that distuils uses these environment - variables when compiling libopenzwave.pyd - MSSDK - DISTUTILS_USE_SDK - - :return: dict of environment variables - """ - path = os.environ.get('Path', '') - - env = dict( - __VSCMD_PREINIT_PATH=path, - Platform=self.platform, - VSCMD_ARG_app_plat='Desktop', - VSCMD_ARG_HOST_ARCH=self.platform, - VSCMD_ARG_TGT_ARCH=self.platform, - __VSCMD_script_err_count='0' - ) - - env_path = set() - - def update_env(cls): - for key, value in cls: - if key == 'Path': - for item in value.split(';'): - env_path.add(item) - continue - - if key in env: - env[key] += ';' + value - else: - env[key] = value - - update_env(self.visual_c) - update_env(self.visual_studio) - update_env(self.windows_sdk) - update_env(self.dot_net) - - env['Path'] = (';'.join(env_path)) + ';' + path - - return env - - def __str__(self): - template = ( - 'Machine architecture: {machine_architecture}\n' - 'Build architecture: {architecture}\n' - ) - - res = [ - template.format( - machine_architecture=self.machine_architecture, - architecture=self.platform - ), - str(self.python), - str(self.visual_studio), - str(self.visual_c), - str(self.windows_sdk), - str(self.dot_net), - ] - - return '\n'.join(res) - - # I have separated the environment into several classes # Environment - the main environment class. # the environment class is what is going to get used. this handles all of the @@ -603,7 +422,7 @@ class VisualCInfo(object): def __init__( self, - environ: Environment, + environ: "Environment", minimum_c_version: Optional[Union[int, float]] = None, strict_c_version: Optional[Union[int, float]] = None, minimum_toolkit_version: Optional[int] = None, @@ -666,6 +485,8 @@ def __init__( self._minimum_toolkit_version = minimum_toolkit_version if _vswhere is not None: + distutils.log.debug('\n' + str(_vswhere)) + cpp_id = 'Microsoft.VisualCpp.Tools.Host{0}.Target{1}'.format( environ.machine_architecture.upper(), environ.python.architecture.upper() @@ -683,9 +504,6 @@ def __init__( cpp_installation = None for installation in _vswhere: - - distutils.log.debug(str(installation) + '\n\n') - if vs_version is not None: try: if isinstance(vs_version, str): @@ -867,6 +685,34 @@ def __init__( if os.path.exists(devinit_path): self._devinit_path = devinit_path + @property + def has_ninja(self): + ide_path = os.path.split(self.ide_install_directory)[0] + ninja_path = os.path.join( + ide_path, + 'CommonExtensions', + 'Microsoft', + 'CMake', + 'Ninja', + 'ninja.exe' + ) + return os.path.exists(ninja_path) + + @property + def has_cmake(self): + ide_path = os.path.split(self.ide_install_directory)[0] + + cmake_path = os.path.join( + ide_path, + 'CommonExtensions', + 'Microsoft', + 'CMake', + 'CMake', + 'bin', + 'cmake.exe' + ) + return os.path.exists(cmake_path) + @property def cmake_paths(self) -> list: paths = [] @@ -1196,37 +1042,6 @@ def toolset_version(self) -> str: tools_version[0] + tools_version[1][:1] ) - # - # - # tool_path = self.tools_redist_directory - # x64 = self.platform == 'x64' - # - # dirs = os.listdir(self.tools_redist_directory) - # - # - # if x64: - # if 'amd64' in dirs: - # tool_path = os.path.join(tool_path, 'amd64') - # else: - # tool_path = os.path.join(tool_path, 'x64') - # - # else: - # tool_path = os.path.join(tool_path, 'x86') - # - # if os.path.exists(tool_path): - # max_ver = 0 - # - # for f in os.listdir(tool_path): - # if not f.endswith('CRT'): - # continue - # try: - # ver = int(f.split('.')[1].lstrip('VC')) - # max_ver = max(max_ver, ver) - # except: - # continue - # - # if max_ver != 0: - # self._toolset_version = 'v' + str(max_ver) return self._toolset_version @@ -1777,8 +1592,10 @@ def __iter__(self): def __str__(self): template = ( '== Visual C ===================================================\n' - ' version: {visual_c_version}\n' - ' path: {visual_c_path}\n' + ' version: {visual_c_version}\n' + ' path: {visual_c_path}\n' + ' has cmake: {has_cmake}\n' + ' has ninja: {has_ninja}\n' '\n' ' -- Tools ---------------------------------------------------\n' ' version: {tools_version}\n' @@ -1806,6 +1623,8 @@ def __str__(self): return template.format( visual_c_version=self.version, visual_c_path=self.install_directory, + has_cmake=self.has_cmake, + has_ninja=self.has_ninja, tools_version=self.tools_version, tools_install_path=self.tools_install_directory, vc_tools_redist_path=self.tools_redist_directory, @@ -1826,7 +1645,7 @@ class VisualStudioInfo(object): def __init__( self, - environ: Environment, + environ: "Environment", c_info: VisualCInfo ): self.environment = environ @@ -2063,8 +1882,9 @@ def __str__(self): return '' template = ( - '== {name}==============================================\n' + '== {name} \n' ' description: {description}\n' + ' install date: {install_date}\n' ' version: {version}\n' ' version (friendly): {product_line_version}\n' ' display version: {product_display_version}\n' @@ -2080,12 +1900,13 @@ def __str__(self): if name is None: if self.__name__ == 'VisualStudioInfo': - name = 'Visual Studio ' + name = 'Visual Studio' else: - name = 'Build Tools ==' + name = 'Build Tools' description = installation.description path = installation.path + install_date = installation.install_date.strftime('%c') version = installation.version is_complete = installation.is_complete is_prerelease = installation.is_prerelease @@ -2097,9 +1918,10 @@ def __str__(self): product_display_version = catalog.product_display_version product_line_version = catalog.product_line_version - return template.format( + res = template.format( name=name, description=description, + install_date=install_date, path=path, version=version, is_complete=is_complete, @@ -2111,12 +1933,16 @@ def __str__(self): state=state ) + res = res.split('\n', 1) + res[0] += '=' * (63 - len(res[0])) + return '\n'.join(res) + class WindowsSDKInfo(object): def __init__( self, - environ: Environment, + environ: "Environment", c_info: VisualCInfo, minimum_sdk_version: Optional[str] = None, strict_sdk_version: Optional[str] = None @@ -2754,7 +2580,6 @@ def directory(self) -> Optional[str]: return self._directory def __iter__(self): - ver_bin_path = self.ver_bin_path directory = self.directory @@ -2833,7 +2658,7 @@ class NETInfo(object): def __init__( self, - environ: Environment, + environ: "Environment", c_info: VisualCInfo, sdk_version: str, minimum_net_version: Optional[str] = None, @@ -3505,6 +3330,202 @@ def __str__(self): ) +class Environment(object): + _original_environment = {k_: v_ for k_, v_ in os.environ.items()} + + def __init__( + self, + minimum_c_version: Optional[Union[int, float]] = None, + strict_c_version: Optional[Union[int, float]] = None, + minimum_toolkit_version: Optional[int] = None, + strict_toolkit_version: Optional[int] = None, + minimum_sdk_version: Optional[str] = None, + strict_sdk_version: Optional[str] = None, + minimum_net_version: Optional[str] = None, + strict_net_version: Optional[str] = None, + vs_version: Optional[Union[str, int]] = None + ): + self.python = PythonInfo() + + self.visual_c = VisualCInfo( + self, + minimum_c_version, + strict_c_version, + minimum_toolkit_version, + strict_toolkit_version, + vs_version + ) + + self.visual_studio = VisualStudioInfo( + self, + self.visual_c + ) + + self.windows_sdk = WindowsSDKInfo( + self, + self.visual_c, + minimum_sdk_version, + strict_sdk_version + ) + + self.dot_net = NETInfo( + self, + self.visual_c, + self.windows_sdk.version, + minimum_net_version, + strict_net_version + ) + + def reset_environment(self): + for key in list(os.environ.keys())[:]: + if key not in self._original_environment: + del os.environ[key] + + os.environ.update(self._original_environment) + + @property + def machine_architecture(self): + import platform + return 'x64' if '64' in platform.machine() else 'x86' + + @property + def platform(self): + """ + :return: x86 or x64 + """ + import platform + + win_64 = self.machine_architecture == 'x64' + python_64 = platform.architecture()[0] == '64bit' and win_64 + + return 'x64' if python_64 else 'x86' + + @property + def configuration(self): + """ + Build configuration + :return: one of ReleaseDLL, DebugDLL + """ + + if os.path.splitext(sys.executable)[0].endswith('_d'): + config = 'Debug' + else: + config = 'Release' + + return config + + def __iter__(self): + for item in self.build_environment.items(): + yield item + + @property + def build_environment(self): + """ + This would be the work horse. This is where all of the gathered + information is put into a single container and returned. + The information is then added to os.environ in order to allow the + build process to run properly. + + List of environment variables generated: + PATH + LIBPATH + LIB + INCLUDE + Platform + FrameworkDir + FrameworkVersion + FrameworkDIR32 + FrameworkVersion32 + FrameworkDIR64 + FrameworkVersion64 + VCToolsRedistDir + VCINSTALLDIR + VCToolsInstallDir + VCToolsVersion + WindowsLibPath + WindowsSdkDir + WindowsSDKVersion + WindowsSdkBinPath + WindowsSdkVerBinPath + WindowsSDKLibVersion + __DOTNET_ADD_32BIT + __DOTNET_ADD_64BIT + __DOTNET_PREFERRED_BITNESS + Framework{framework version}Version + NETFXSDKDir + UniversalCRTSdkDir + UCRTVersion + ExtensionSdkDir + + These last 2 are set to ensure that distuils uses these environment + variables when compiling libopenzwave.pyd + MSSDK + DISTUTILS_USE_SDK + + :return: dict of environment variables + """ + path = os.environ.get('Path', '') + + env = dict( + __VSCMD_PREINIT_PATH=path, + Platform=self.platform, + VSCMD_ARG_app_plat='Desktop', + VSCMD_ARG_HOST_ARCH=self.platform, + VSCMD_ARG_TGT_ARCH=self.platform, + __VSCMD_script_err_count='0' + ) + + path = set(item.strip() for item in path.split(';') if item.strip()) + env_path = set() + + def update_env(cls): + for key, value in cls: + if key == 'Path': + for item in value.split(';'): + env_path.add(item) + continue + + if key in env: + env[key] += ';' + value + else: + env[key] = value + + update_env(self.visual_c) + update_env(self.visual_studio) + update_env(self.windows_sdk) + update_env(self.dot_net) + + env_path = [item for item in env_path if item not in path] + env_path = ';'.join(env_path) + + if env_path: + env_path += ';' + + env['Path'] = env_path + ';'.join(path) + + return env + + def __str__(self): + template = ( + 'Machine architecture: {machine_architecture}\n' + 'Build architecture: {architecture}\n' + ) + + res = [ + template.format( + machine_architecture=self.machine_architecture, + architecture=self.platform + ), + str(self.python), + str(self.visual_studio), + str(self.visual_c), + str(self.windows_sdk), + str(self.dot_net), + ] + + return '\n'.join(res) + + def setup_environment( minimum_c_version: Optional[Union[int, float]] = None, strict_c_version: Optional[Union[int, float]] = None, @@ -3611,6 +3632,15 @@ def setup_environment( distutils.log.debug('\n' + str(environment)) for key, value in environment.build_environment.items(): + old_val = os.environ.get(key, value) + if old_val != value: + if ';' in old_val or ';' in value: + old_val = set(old_val.split(';')) + value = set(';'.split(value)) + + value = old_val.union(value) + value = ';'.join(item for item in value) + os.environ[key] = value return environment diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py index bd025246a2..f45ac05d07 100644 --- a/buildtools/msvc/vswhere.py +++ b/buildtools/msvc/vswhere.py @@ -24,67 +24,814 @@ # # ############################################################################# -try: - import comtypes -except ImportError: - raise RuntimeError('the comtypes library is needed to run this script') + +# ############################################################################# +# +# This software is OSI Certified Open Source Software. +# OSI Certified is a certification mark of the Open Source Initiative. +# +# Copyright (c) 2006-2013, Thomas Heller. +# Copyright (c) 2014, Comtypes Developers. +# All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ############################################################################# import weakref import ctypes +import sys +import atexit +import datetime +from _ctypes import COMError +from ctypes import POINTER, HRESULT from ctypes.wintypes import ( - LPFILETIME, LPCOLESTR, ULONG, + USHORT, LPCWSTR, LPVOID, - LCID -) - -from comtypes.automation import ( - tagVARIANT, - BSTR -) -from comtypes import ( - GUID, - COMMETHOD, - POINTER, - IUnknown, - HRESULT -) -from comtypes._safearray import ( # NOQA - SAFEARRAY, + LCID, + DWORD, + LONG, + WORD, + BYTE, + INT, + BOOL, + SHORT, + UINT, + FLOAT, + DOUBLE, VARIANT_BOOL, - SafeArrayLock, - SafeArrayUnlock ) -LPVARIANT = POINTER(tagVARIANT) -VARIANT = tagVARIANT +UBYTE = ctypes.c_ubyte +ULONGLONG = ctypes.c_ulonglong +LONGLONG = ctypes.c_longlong +VARTYPE = USHORT +PVOID = ctypes.c_void_p ENUM = ctypes.c_uint -IID = GUID -CLSID = GUID -MAXUINT = 0xFFFFFFFF PULONGLONG = POINTER(ctypes.c_ulonglong) -LPSAFEARRAY = POINTER(SAFEARRAY) + +_oleaut32 = ctypes.windll.oleaut32 +_ole32_nohresult = ctypes.windll.ole32 +_ole32 = ctypes.oledll.ole32 +_kernel32 = ctypes.windll.kernel32 + +_StringFromCLSID = _ole32.StringFromCLSID _CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree +_CLSIDFromString = _ole32.CLSIDFromString +_VariantClear = _oleaut32.VariantClear + +_SafeArrayLock = _oleaut32.SafeArrayLock +_SafeArrayLock.restype = HRESULT -kernel32 = ctypes.windll.kernel32 +_SafeArrayUnlock = _oleaut32.SafeArrayUnlock +_SafeArrayUnlock.restype = HRESULT -_GetUserDefaultLCID = kernel32.GetUserDefaultLCID +_GetUserDefaultLCID = _kernel32.GetUserDefaultLCID _GetUserDefaultLCID.restype = LCID +_FileTimeToSystemTime = _kernel32.FileTimeToSystemTime +_FileTimeToSystemTime.restype = BOOL -def HRESULT_FROM_WIN32(x): - return x +_SystemTimeToFileTime = _kernel32.SystemTimeToFileTime +_SystemTimeToFileTime.restype = BOOL +ctypes.pythonapi.PyInstanceMethod_New.argtypes = [ctypes.py_object] +ctypes.pythonapi.PyInstanceMethod_New.restype = ctypes.py_object +PyInstanceMethod_Type = type(ctypes.pythonapi.PyInstanceMethod_New(id)) + +CLSCTX_SERVER = 5 +CLSCTX_ALL = 7 +COINIT_APARTMENTTHREADED = 0x2 +MAXUINT = 0xFFFFFFFF ERROR_FILE_NOT_FOUND = 0x00000002 ERROR_NOT_FOUND = 0x00000490 + +def HRESULT_FROM_WIN32(x): + return x + + # Constants E_NOTFOUND = HRESULT_FROM_WIN32(ERROR_NOT_FOUND) E_FILENOTFOUND = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) +VT_EMPTY = 0 +VT_NULL = 1 +VT_I2 = 2 +VT_I4 = 3 +VT_R4 = 4 +VT_R8 = 5 +VT_CY = 6 +VT_DATE = 7 +VT_BSTR = 8 +VT_BOOL = 11 +VT_I1 = 16 +VT_UI1 = 17 +VT_UI2 = 18 +VT_UI4 = 19 +VT_I8 = 20 +VT_UI8 = 21 +VT_INT = 22 +VT_UINT = 23 + + +_PARAMFLAGS = { + "in": 1, + "out": 2, + "lcid": 4, + "retval": 8, + "optional": 16, +} + + +def instancemethod(func, inst, _): + mth = PyInstanceMethod_Type(func) + if inst is None: + return mth + return mth.__get__(inst) + + +def CoInitialize(): + flags = getattr(sys, "coinit_flags", COINIT_APARTMENTTHREADED) + _ole32.CoInitializeEx(None, flags) + + +def CoUninitialize(): + _ole32_nohresult.CoUninitialize() + + +def CoCreateInstance(clsid, interface=None, clsctx=None, punkouter=None): + if clsctx is None: + clsctx = CLSCTX_SERVER + + if interface is None: + interface = IUnknown + + p = POINTER(interface)() + iid = interface._iid_ # NOQA + _ole32.CoCreateInstance( + ctypes.byref(clsid), + punkouter, + clsctx, + ctypes.byref(iid), + ctypes.byref(p) + ) + return p + + +def _shutdown( + func=_ole32_nohresult.CoUninitialize, + _exc_clear=getattr(sys, "exc_clear", lambda: None) +): + _exc_clear() + + try: + func() + except WindowsError: + pass + + if _cominterface_meta is not None: + _cominterface_meta._com_shutting_down = True + + +class ReferenceEmptyClass(object): + pass + + +class Patch(object): + def __init__(self, target): + self.target = target + + def __call__(self, patches): + for name, value in list(vars(patches).items()): + if name in vars(ReferenceEmptyClass): + continue + n_replace = getattr(value, '__no_replace', False) + if n_replace and hasattr(self.target, name): + continue + + setattr(self.target, name, value) + + +def no_replace(f): + f.__no_replace = True + return f + + +class tagSAFEARRAYBOUND(ctypes.Structure): + _fields_ = [ + ('cElements', DWORD), + ('lLbound', LONG), + ] + + +SAFEARRAYBOUND = tagSAFEARRAYBOUND + + +class tagSAFEARRAY(ctypes.Structure): + _fields_ = [ + ('cDims', USHORT), + ('fFeatures', USHORT), + ('cbElements', DWORD), + ('cLocks', DWORD), + ('pvData', PVOID), + ('rgsabound', SAFEARRAYBOUND * 1), + ] + + +SAFEARRAY = tagSAFEARRAY +LPSAFEARRAY = POINTER(SAFEARRAY) + +_SafeArrayLock.argtypes = [POINTER(SAFEARRAY)] +_SafeArrayUnlock.argtypes = [POINTER(SAFEARRAY)] + + +class BSTR(ctypes.c_wchar_p): + _needsfree = False + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, self.value) + + def __ctypes_from_outparam__(self): + self._needsfree = True + return self.value + + def __del__(self, _free=_oleaut32.SysFreeString): + if self._b_base_ is None or self._needsfree: # NOQA + _free(self) + + def from_param(cls, value): + if isinstance(value, cls): + return value + + return cls(value) + + from_param = classmethod(from_param) + + +class GUID(ctypes.Structure): + _fields_ = [ + ("Data1", DWORD), + ("Data2", WORD), + ("Data3", WORD), + ("Data4", BYTE * 8) + ] + + def __init__(self, name=None): + ctypes.Structure.__init__(self) + + if name is not None: + _CLSIDFromString(str(name), ctypes.byref(self)) + + def __repr__(self): + return 'GUID("%s")' % str(self) + + def __str__(self): + p = ctypes.c_wchar_p() + _StringFromCLSID(ctypes.byref(self), ctypes.byref(p)) + result = p.value + _CoTaskMemFree(p) + return result + + def __eq__(self, other): + # noinspection PyTypeChecker + return isinstance(other, GUID) and bytes(self) == bytes(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + # noinspection PyTypeChecker + return hash(bytes(self)) + + +IID = GUID +CLSID = GUID + + +def _encode_idl(names): + # sum up all values found in _PARAMFLAGS, ignoring all others. + return sum([_PARAMFLAGS.get(n, 0) for n in names]) + + +def COMMETHOD(idlflags, restype, methodname, *argspec): + paramflags = [] + argtypes = [] + + for item in argspec: + idl, typ, argname = item + pflags = _encode_idl(idl) + paramflags.append((pflags, argname)) + argtypes.append(typ) + + return ( + restype, + methodname, + tuple(argtypes), + tuple(paramflags), + tuple(idlflags), + None + ) + + +com_interface_registry = {} + + +class _cominterface_meta(type): + _com_shutting_down = False + + def __new__(self, name, bases, namespace): # NOQA + methods = namespace.pop("_methods_", None) + cls = type.__new__(self, name, bases, namespace) + + if methods is not None: + cls._methods_ = methods + + if bases == (object,): + _ptr_bases = (cls, _compointer_base) + else: + _ptr_bases = (cls, POINTER(bases[0])) + + p = type(_compointer_base)( + "POINTER(%s)" % cls.__name__, + _ptr_bases, + { + "__com_interface__": cls, + "_needs_com_addref_": None + } + ) + + from ctypes import _pointer_type_cache # NOQA + _pointer_type_cache[cls] = p + + @Patch(POINTER(p)) + class ReferenceFix(object): # NOQA + def __setitem__(self, index, value): + if index != 0: + if bool(value): + value.AddRef() + + super(POINTER(p), self).__setitem__(index, value) # NOQA + return + + from _ctypes import CopyComPointer + CopyComPointer(value, self) + + return cls + + def __setattr__(self, name, value): + if name == "_methods_": + self._make_methods(value) + + type.__setattr__(self, name, value) + + def __get_baseinterface_methodcount(self): + itf_name = None + try: + result = 0 + for itf in self.mro()[1:-1]: + itf_name = itf.__name__ + result += len(itf.__dict__["_methods_"]) + return result + + except KeyError as err: + (name,) = err.args + if name == "_methods_": + raise TypeError( + "baseinterface '%s' has no _methods_" % itf_name + ) + raise + + def _fix_inout_args(self, func, argtypes, paramflags): # NOQA + SIMPLETYPE = type(INT) + BYREFTYPE = type(ctypes.byref(INT())) + + def call_with_inout(self_, *args, **kw): + args = list(args) + outargs = {} + outnum = 0 + for i, info in enumerate(paramflags): + direction = info[0] + if direction & 3 == 3: + name = info[1] + atyp = argtypes[i]._type_ # NOQA + + try: + try: + v = args[i] + except IndexError: + v = kw[name] + except KeyError: + v = atyp() + else: + if getattr(v, "_type_", None) is atyp: + pass + elif type(atyp) is SIMPLETYPE: + v = atyp(v) + else: + v = atyp.from_param(v) + assert not isinstance(v, BYREFTYPE) + outargs[outnum] = v + outnum += 1 + if len(args) > i: + args[i] = v + else: + kw[name] = v + elif direction & 2 == 2: + outnum += 1 + + rescode = func(self_, *args, **kw) + + if outnum == 1: + if len(outargs) == 1: + rescode = rescode.__ctypes_from_outparam__() + return rescode + + rescode = list(rescode) + for outnum, o in list(outargs.items()): + rescode[outnum] = o.__ctypes_from_outparam__() + return rescode + + return call_with_inout + + def _make_methods(self, methods): + iid = self.__dict__["_iid_"] + + iid = str(iid) + com_interface_registry[iid] = self + del iid + + vtbl_offset = self.__get_baseinterface_methodcount() + + for i, item in enumerate(methods): + restype, name, argtypes, paramflags, idlflags, doc = item + prototype = ctypes.WINFUNCTYPE(restype, *argtypes) + + if restype == HRESULT: + # noinspection PyTypeChecker + raw_func = prototype( + i + vtbl_offset, + name, + None, + self._iid_ # NOQA + ) + + func = prototype( + i + vtbl_offset, + name, + paramflags, + self._iid_ # NOQA + ) + else: + # noinspection PyTypeChecker + raw_func = prototype(i + vtbl_offset, name, None, None) + # noinspection PyTypeChecker + func = prototype(i + vtbl_offset, name, paramflags, None) + + setattr( + self, + "_%s__com_%s" % (self.__name__, name), + instancemethod(raw_func, None, self) + ) + + if paramflags: + dirflags = [(p[0] & 3) for p in paramflags] + if 3 in dirflags: + func = self._fix_inout_args(func, argtypes, paramflags) + + func.__doc__ = doc + func.__name__ = name + + mth = instancemethod(func, None, self) + + if hasattr(self, name): + setattr(self, "_" + name, mth) + else: + setattr(self, name, mth) + + +class _compointer_meta(type(ctypes.c_void_p), _cominterface_meta): + pass + + +class _compointer_base(ctypes.c_void_p, metaclass=_compointer_meta): + + def __del__(self): + if self: + if not type(self)._com_shutting_down: # NOQA + self.Release() # NOQA + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + if not isinstance(other, _compointer_base): + return False + + val1 = super(_compointer_base, self).value + val2 = super(_compointer_base, other).value + + return val1 == val2 + + def __hash__(self): + return hash(super(_compointer_base, self).value) + + def __get_value(self): + return self + + value = property(__get_value, doc="""Return self.""") + + def __repr__(self): + ptr = super(_compointer_base, self).value + return "<%s ptr=0x%x at %x>" % ( + self.__class__.__name__, + ptr or 0, + id(self) + ) + + def from_param(cls, value): + if value is None: + return None + if value == 0: + return None + if isinstance(value, cls): + return value + + if cls._iid_ == getattr(value, "_iid_", None): # NOQA + return value + + try: + table = value._com_pointers_ # NOQA + except AttributeError: + pass + else: + try: + return table[cls._iid_] # NOQA + except KeyError: + raise TypeError( + "Interface %s not supported" % cls._iid_ # NOQA + ) + + return value.QueryInterface(cls.__com_interface__) # NOQA + + from_param = classmethod(from_param) + + +class IUnknown(object, metaclass=_cominterface_meta): + _case_insensitive_ = False + _iid_ = GUID("{00000000-0000-0000-C000-000000000046}") + + _methods_ = [ + COMMETHOD( + [], + HRESULT, + "QueryInterface", + (['in'], POINTER(GUID), "riid"), + (['in'], POINTER(PVOID), "ppvObject") + ), + COMMETHOD( + [], + ULONG, + "AddRef" + ), + COMMETHOD( + [], + ULONG, + "Release" + ) + ] + + def QueryInterface(self, interface, iid=None): + p = POINTER(interface)() + + if iid is None: + iid = interface._iid_ # NOQA + + self.__com_QueryInterface(ctypes.byref(iid), ctypes.byref(p)) # NOQA + + clsid = self.__dict__.get('__clsid') + if clsid is not None: + p.__dict__['__clsid'] = clsid + + return p + + def AddRef(self): + return self.__com_AddRef() # NOQA + + def Release(self): + return self.__com_Release() # NOQA + + +class tagDEC(ctypes.Structure): + _fields_ = [ + ("wReserved", ctypes.c_ushort), + ("scale", ctypes.c_ubyte), + ("sign", ctypes.c_ubyte), + ("Hi32", ctypes.c_ulong), + ("Lo64", ctypes.c_ulonglong) + ] + + +DECIMAL = tagDEC + + +class _FILETIME(ctypes.Structure): + _fields_ = [ + ('dwLowDateTime', DWORD), + ('dwHighDateTime', DWORD) + ] + + @property + def value(self): + system_time = SYSTEMTIME() + _FileTimeToSystemTime(ctypes.byref(self), ctypes.byref(system_time)) + return system_time.value + + @value.setter + def value(self, dt): + system_time = SYSTEMTIME() + system_time.value = dt + _SystemTimeToFileTime(ctypes.byref(system_time), ctypes.byref(self)) + + +FILETIME = _FILETIME +LPFILETIME = POINTER(FILETIME) + + +class _SYSTEMTIME(ctypes.Structure): + _fields_ = [ + ('wYear', WORD), + ('wMonth', WORD), + ('wDayOfWeek', WORD), + ('wDay', WORD), + ('wHour', WORD), + ('wMinute', WORD), + ('wSecond', WORD), + ('wMilliseconds', WORD), + ] + + @property + def value(self): + dt = datetime.datetime( + year=self.wYear, + month=self.wMonth, + day=self.wDay, + hour=self.wHour, + minute=self.wMinute, + second=self.wSecond, + microsecond=self.wMilliseconds * 1000 + ) + + return dt + + # noinspection PyAttributeOutsideInit + @value.setter + def value(self, dt): + if isinstance(dt, (int, float)): + dt = datetime.datetime.fromtimestamp(dt) + + weekday = dt.weekday() + 1 + if weekday == 7: + weekday = 0 + + self.wYear = dt.year + self.wMonth = dt.month + self.wDayOfWeek = weekday + self.wDay = dt.day + self.wHour = dt.hour + self.wMinute = dt.minute + self.wSecond = dt.second + self.wMilliseconds = int(dt.microsecond / 1000) + + +SYSTEMTIME = _SYSTEMTIME + + +class tagVARIANT(ctypes.Structure): + class U_VARIANT1(ctypes.Union): + class __tagVARIANT(ctypes.Structure): + class U_VARIANT2(ctypes.Union): + class _tagBRECORD(ctypes.Structure): + # noinspection PyTypeChecker + _fields_ = [ + ("pvRecord", PVOID), + ("pRecInfo", POINTER(IUnknown)) + ] + + _fields_ = [ + ("VT_BOOL", VARIANT_BOOL), + ("VT_I1", BYTE), + ("VT_I2", SHORT), + ("VT_I4", LONG), + ("VT_I8", LONGLONG), + ("VT_INT", INT), + ("VT_UI1", UBYTE), + ("VT_UI2", USHORT), + ("VT_UI4", ULONG), + ("VT_UI8", ULONGLONG), + ("VT_UINT", UINT), + ("VT_R4", FLOAT), + ("VT_R8", DOUBLE), + ("VT_CY", LONGLONG), + ("c_wchar_p", ctypes.c_wchar_p), + ("c_void_p", PVOID), + ("pparray", POINTER(POINTER(tagSAFEARRAY))), + ("bstrVal", BSTR), + ("_tagBRECORD", _tagBRECORD), + ] + _anonymous_ = ["_tagBRECORD"] + + _fields_ = [ + ("vt", VARTYPE), + ("wReserved1", USHORT), + ("wReserved2", USHORT), + ("wReserved3", USHORT), + ("_", U_VARIANT2) + ] + + _fields_ = [ + ("__VARIANT_NAME_2", __tagVARIANT), + ("decVal", DECIMAL) + ] + _anonymous_ = ["__VARIANT_NAME_2"] + + _fields_ = [ + ("__VARIANT_NAME_1", U_VARIANT1) + ] + _anonymous_ = ["__VARIANT_NAME_1"] + + def __init__(self): + ctypes.Structure.__init__(self) + + def __del__(self): + if self._b_needsfree_: + _VariantClear(self) + + @property + def value(self): + vt = self.vt + if vt in (VT_EMPTY, VT_NULL): + return None + elif vt == VT_I1: + return self._.VT_I1 + elif vt == VT_I2: + return self._.VT_I2 + elif vt == VT_I4: + return self._.VT_I4 + elif vt == VT_I8: + return self._.VT_I8 + elif vt == VT_UI8: + return self._.VT_UI8 + elif vt == VT_INT: + return self._.VT_INT + elif vt == VT_UI1: + return self._.VT_UI1 + elif vt == VT_UI2: + return self._.VT_UI2 + elif vt == VT_UI4: + return self._.VT_UI4 + elif vt == VT_UINT: + return self._.VT_UINT + elif vt == VT_R4: + return self._.VT_R4 + elif vt == VT_R8: + return self._.VT_R8 + elif vt == VT_BOOL: + return self._.VT_BOOL + elif vt == VT_BSTR: + return self._.bstrVal + + def __ctypes_from_outparam__(self): + result = self.value + self.vt = VT_EMPTY + return result + + +LPVARIANT = POINTER(tagVARIANT) +VARIANT = tagVARIANT + +_VariantClear.argtypes = (POINTER(VARIANT),) + # Enumerations # The state of an instance. @@ -414,7 +1161,7 @@ def id(self): @property def install_date(self): # noinspection PyUnresolvedReferences - return self.GetInstallDate() + return self.GetInstallDate().value @property def name(self): @@ -441,7 +1188,7 @@ def display_name(self): try: # noinspection PyUnresolvedReferences return self.GetDisplayName(_GetUserDefaultLCID()) - except (OSError, ValueError, comtypes.COMError): + except (OSError, ValueError, COMError): pass @property @@ -449,19 +1196,21 @@ def description(self): try: # noinspection PyUnresolvedReferences return self.GetDescription(_GetUserDefaultLCID()) - except (OSError, ValueError, comtypes.COMError): + except (OSError, ValueError, COMError): pass def __str__(self): + title_bar = '-- ' + str(self.display_name) + ' ' + title_bar += '-' * (63 - len(title_bar)) res = [ + title_bar, + 'description: ' + str(self.description), + 'version: ' + str(self.version), 'id: ' + str(self.id), 'name: ' + str(self.name), - 'display name: ' + str(self.display_name), - 'description: ' + str(self.description), 'path: ' + str(self.path), - 'version: ' + str(self.version), 'full version: ' + str(self.full_version), - 'install date: ' + str(self.install_date) + 'install date: ' + self.install_date.strftime('%c') ] return '\n'.join(res) @@ -478,10 +1227,10 @@ def packages(self) -> Packages: # noinspection PyUnresolvedReferences safearray = self.GetPackages() - SafeArrayLock(safearray) + _SafeArrayLock(safearray) # noinspection PyTypeChecker - packs = comtypes.cast( + packs = ctypes.cast( safearray.contents.pvData, POINTER(POINTER(ISetupPackageReference)) ) @@ -493,7 +1242,7 @@ def packages(self) -> Packages: p = packs[i] res.append(p) - SafeArrayUnlock(safearray) + _SafeArrayUnlock(safearray) res = Packages(res) return res @@ -549,7 +1298,7 @@ def is_complete(self): @property def is_prerelease(self): catalog = self.QueryInterface(ISetupInstanceCatalog) - return catalog.IsPrerelease() + return catalog.IsPrerelease() # NOQA @property def catalog(self): @@ -582,7 +1331,8 @@ def __str__(self): 'properties:', '{properties}', 'catalog:', - '{catalog}' + '{catalog}', + '-' * 63 ] res = '\n'.join(res) @@ -740,7 +1490,7 @@ def __iter__(self): set_instance, num = self.Next(1) yield set_instance - except comtypes.COMError: + except COMError: break @@ -754,7 +1504,7 @@ class ISetupConfiguration(IUnknown): def __call__(self): try: return self.QueryInterface(ISetupConfiguration2) - except ValueError: + except (ValueError, OSError, COMError): return self def __iter__(self): @@ -768,6 +1518,13 @@ def __iter__(self): yield si(helper) + def __str__(self): + res = [] + for instance_config in self: + res += [str(instance_config)] + + return '\n\n\n'.join(res) + IID_ISetupConfiguration2 = IID("{26AAB78C-4A60-49D6-AF3B-3C35BC93365D}") @@ -810,10 +1567,10 @@ def failed_packages(self) -> Packages: except ValueError: return Packages([]) - SafeArrayLock(safearray) + _SafeArrayLock(safearray) # noinspection PyTypeChecker - packs = comtypes.cast( + packs = ctypes.cast( safearray.contents.pvData, POINTER(POINTER(ISetupFailedPackageReference)) ) @@ -826,7 +1583,7 @@ def failed_packages(self) -> Packages: p = p.QueryInterface(ISetupFailedPackageReference2) res.append(p) - SafeArrayUnlock(safearray) + _SafeArrayUnlock(safearray) res = Packages(res) return res @@ -838,10 +1595,10 @@ def skipped_packages(self) -> Packages: except ValueError: return Packages([]) - SafeArrayLock(safearray) + _SafeArrayLock(safearray) # noinspection PyTypeChecker - packs = comtypes.cast( + packs = ctypes.cast( safearray.contents.pvData, POINTER(POINTER(ISetupFailedPackageReference)) ) @@ -854,7 +1611,7 @@ def skipped_packages(self) -> Packages: p = p.QueryInterface(ISetupFailedPackageReference2) res.append(p) - SafeArrayUnlock(safearray) + _SafeArrayUnlock(safearray) res = Packages(res) return res @@ -950,16 +1707,16 @@ def names(self): # noinspection PyUnresolvedReferences safearray = self.GetNames() - SafeArrayLock(safearray) + _SafeArrayLock(safearray) - names = comtypes.cast(safearray.contents.pvData, POINTER(BSTR)) + names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) cPackages = safearray.contents.rgsabound[0].cElements res = [] for i in range(cPackages): res.append(names[i]) - SafeArrayUnlock(safearray) + _SafeArrayUnlock(safearray) return res @@ -968,7 +1725,7 @@ def __iter__(self): # noinspection PyUnresolvedReferences v = VARIANT() - self.GetValue(n, ctypes.byref(v)) + self.GetValue(n, ctypes.byref(v)) # NOQA v = v.value if isinstance(v, BSTR): @@ -994,16 +1751,16 @@ def names(self): # noinspection PyUnresolvedReferences safearray = self.GetNames() - SafeArrayLock(safearray) + _SafeArrayLock(safearray) - names = comtypes.cast(safearray.contents.pvData, POINTER(BSTR)) + names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) cPackages = safearray.contents.rgsabound[0].cElements res = [] for i in range(cPackages): res.append(names[i]) - SafeArrayUnlock(safearray) + _SafeArrayUnlock(safearray) return res @@ -1012,7 +1769,7 @@ def __iter__(self): # noinspection PyUnresolvedReferences v = VARIANT() - self.GetValue(n, ctypes.byref(v)) + self.GetValue(n, ctypes.byref(v)) # NOQA v = v.value if isinstance(v, BSTR): @@ -1485,7 +2242,6 @@ class SetupConfiguration(IUnknown): # Gets an ISetupConfiguration that provides information about # product instances installed on the machine. - # noinspection PyTypeChecker _methods_ = [ COMMETHOD( @@ -1504,16 +2260,17 @@ class SetupConfiguration(IUnknown): @classmethod def _callback(cls, _): cls._instance_ = None - comtypes.CoUninitialize() + CoUninitialize() @classmethod def GetSetupConfiguration(cls): if cls._instance_ is None: - comtypes.CoInitialize() - instance = comtypes.CoCreateInstance( + CoInitialize() + # noinspection PyCallingNonCallable + instance = CoCreateInstance( CLSID_SetupConfiguration, ISetupConfiguration, - comtypes.CLSCTX_ALL + CLSCTX_ALL )() cls._instance_ = weakref.ref(instance, cls._callback) @@ -1523,7 +2280,9 @@ def GetSetupConfiguration(cls): return instance +atexit.register(_shutdown) + + if __name__ == '__main__': setup_config = SetupConfiguration.GetSetupConfiguration() - for instance_config in setup_config: - print(instance_config) + print(setup_config) diff --git a/requirements/devel.txt b/requirements/devel.txt index 2b8c2edfa6..18164bdcc9 100644 --- a/requirements/devel.txt +++ b/requirements/devel.txt @@ -19,5 +19,3 @@ sphinx==2.2.0 ; python_version >= '3.0' sphinx==1.8.5 ; python_version < '3.0' doc2dash==2.3.0 beautifulsoup4 - -https://github.com/kdschlosser/comtypes/tarball/bstr_type; sys_platform == 'win32' From cb7eb70afb575e9a0c5cf91872a7fd396a8b3a09 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 25 Jan 2022 12:02:28 -0700 Subject: [PATCH 10/23] Fixes some environment settings --- buildtools/msvc/__init__.py | 178 +++++++++++++++++++++++------------- 1 file changed, 114 insertions(+), 64 deletions(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index 5a8319111c..3881931bf7 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -1512,6 +1512,10 @@ def lib_path(self) -> list: if os.path.exists(references_path): lib_path += [references_path] + else: + references_path = os.path.join(path, 'x86', 'store', 'references') + if os.path.exists(references_path): + lib_path += [references_path] return lib_path @@ -1572,11 +1576,16 @@ def __iter__(self): VCToolsRedistDir=self.tools_redist_directory, Path=self.path, LIB=self.lib, - Include=self.include, + INCLUDE=self.include, LIBPATH=self.lib_path, FSHARPINSTALLDIR=self.f_sharp_path ) + html_help = self.html_help_path + + if html_help is not None: + env['HTMLHelpDir'] = html_help + if self._product_semantic_version is not None: env['VSCMD_VER'] = self._product_semantic_version @@ -2601,7 +2610,7 @@ def __iter__(self): LIB=self.lib, Path=self.path, LIBPATH=self.lib_path, - Include=self.include, + INCLUDE=self.include, UniversalCRTSdkDir=self.ucrt_sdk_directory, ExtensionSdkDir=self.extension_sdk_directory, WindowsSdkVerBinPath=ver_bin_path, @@ -3016,20 +3025,45 @@ def preferred_bitness(self) -> str: return '32' if self.platform == 'x86' else '64' @property - def _net_fx_versions(self): - import fnmatch + def netfx_sdk_directory(self) -> Optional[str]: + framework = '.'.join(self.version[1:].split('.')[:2]) + ver = float(int(self.vc_version.split('.')[0])) + if ver in (9.0, 10.0, 11.0, 12.0): + key = 'Microsoft SDKs\\Windows\\v{0}\\'.format(self.sdk_version) + else: + key = 'Microsoft SDKs\\NETFXSDK\\{0}\\'.format(framework) + + net_fx_path = _get_reg_value( + key, + 'KitsInstallationFolder', + wow6432=True + ) + + if net_fx_path and os.path.exists(net_fx_path): + return net_fx_path + + @property + def net_fx_tools_directory(self) -> Optional[str]: framework = self.version[1:].split('.')[:2] + + if framework[0] == '4': + net_framework = '40' + else: + net_framework = ''.join(framework) + net_fx_key = ( 'WinSDK-NetFx{framework}Tools-{platform}' ).format( - framework=''.join(framework), + framework=''.join(net_framework), platform=self.platform ) + + framework = '.'.join(framework) ver = float(int(self.vc_version.split('.')[0])) if ver in (9.0, 10.0, 11.0, 12.0): - key = 'Microsoft SDKs\\Windows\\v{0}\\{1}'.format( + key = 'Microsoft SDKs\\Windows\\v{0}\\{1}\\'.format( self.sdk_version, net_fx_key ) @@ -3037,56 +3071,24 @@ def _net_fx_versions(self): if self.sdk_version in ('6.0A', '6.1'): key = key.replace(net_fx_key, 'WinSDKNetFxTools') - keys = (key,) + net_fx_path = _get_reg_value( + key, + 'InstallationFolder', + wow6432=True + ) else: - keys = ( - 'Microsoft SDKs\\NETFXSDK\\3.5*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.0*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.5*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.6*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.7*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.8*\\' + net_fx_key, - 'Microsoft SDKs\\NETFXSDK\\4.6*\\' + net_fx_key, - 'Microsoft SDKs\\Windows\\v8.1\\' + net_fx_key, - 'Microsoft SDKs\\Windows\\v10.0\\' + net_fx_key, + key = 'Microsoft SDKs\\NETFXSDK\\{0}\\{1}\\'.format( + framework, + net_fx_key ) - - for key in keys: - if '*' in key: - for fx_ver in _read_reg_keys('Microsoft SDKs\\NETFXSDK'): - fx_ver = 'Microsoft SDKs\\NETFXSDK\\{0}\\{1}'.format( - fx_ver, - net_fx_key - ) - - if fnmatch.fnmatch(fx_ver, key): - yield fx_ver - else: - val = _get_reg_value( - key + '\\', - 'InstallationFolder' - ) - if val: - yield val - - @property - def netfx_sdk_directory(self) -> Optional[str]: - for key in self._net_fx_versions: net_fx_path = _get_reg_value( - key.rsplit('\\', 1)[0], - 'KitsInstallationFolder' + key, + 'InstallationFolder', + wow6432=True ) - if net_fx_path and os.path.exists(net_fx_path): - return net_fx_path - - @property - def net_fx_tools_directory(self) -> Optional[str]: - for key in self._net_fx_versions: - net_fx_path = _get_reg_value(key, 'InstallationFolder') - - if net_fx_path and os.path.exists(net_fx_path): - return net_fx_path + if net_fx_path and os.path.exists(net_fx_path): + return net_fx_path @property def add(self) -> str: @@ -3242,16 +3244,13 @@ def lib_path(self) -> list: @property def include(self) -> list: - net_fx_tools = self.net_fx_tools_directory + sdk_directory = self.netfx_sdk_directory - if net_fx_tools: - net_fx_tools = os.path.join( - net_fx_tools, - 'include', - 'um' - ) - if os.path.exists(net_fx_tools): - return [net_fx_tools] + if sdk_directory: + include_dir = os.path.join(sdk_directory, 'include', 'um') + + if os.path.exists(include_dir): + return [include_dir] return [] @@ -3266,31 +3265,82 @@ def __iter__(self): LIB=self.lib, Path=self.path, LIBPATH=self.lib_path, - Include=self.include, + INCLUDE=self.include, __DOTNET_PREFERRED_BITNESS=self.preferred_bitness, FrameworkDir=directory, - FrameworkVersion=self.version, NETFXSDKDir=self.netfx_sdk_directory, ) + version = self.version[1:].split('.') + if version[0] == '4': + version = ['4', '0'] + else: + version = version[:2] + + net_p = 'v' + ('.'.join(version)) + env[self.add] = '1' if self.platform == 'x64': directory_64 = self.directory_64 if directory_64: + loc = os.path.join(directory_64, net_p) + + if os.path.exists(loc): + version_64 = loc + else: + for p in os.listdir(directory_64): + if not os.path.isdir(os.path.join(directory_64, p)): + continue + + if p[1:] < net_p[1:]: + continue + + version_64 = p + break + else: + version_64 = self.version_64 + directory_64 += '\\' + else: + version_64 = None env['FrameworkDir64'] = directory_64 - env['FrameworkVersion64'] = self.version_64 + env['FrameworkVersion64'] = version_64 + env['FrameworkVersion'] = version_64 + else: directory_32 = self.directory_32 + if directory_32: + loc = os.path.join(directory_32, net_p) + + if not os.path.exists(loc): + for p in os.listdir(directory_32): + if not os.path.isdir(os.path.join(directory_32, p)): + continue + + if p[1:] < net_p[1:]: + continue + + version_32 = p + break + else: + version_32 = self.version_64 + + else: + version_32 = loc + directory_32 += '\\' + else: + version_32 = None env['FrameworkDir32'] = directory_32 - env['FrameworkVersion32'] = self.version_32 + env['FrameworkVersion32'] = version_32 + env['FrameworkVersion'] = version_32 framework = env['FrameworkVersion'][1:].split('.')[:2] + framework_version_key = ( 'Framework{framework}Version'.format(framework=''.join(framework)) ) From 8c637d8013a1198c4b057e714dfb6bf595440771 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 25 Jan 2022 22:13:16 -0700 Subject: [PATCH 11/23] adds debugging output for msvc environment --- buildtools/msvc/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index 3881931bf7..c4c8c5fb6a 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -3637,6 +3637,9 @@ def setup_environment( :return: Environment instance :rtype: Environment """ + + distutils.log.set_threshold(distutils.log.DEBUG) + if not _IS_WIN: raise RuntimeError( 'This script will only work with a Windows opperating system.' @@ -3679,7 +3682,10 @@ def setup_environment( vs_version ) - distutils.log.debug('\n' + str(environment)) + distutils.log.debug('\n' + str(environment) + '\n\n') + distutils.log.debug('SET ENVIRONMENT VARIABLES') + distutils.log.debug('------------------------------------------------') + distutils.log.debug('\n') for key, value in environment.build_environment.items(): old_val = os.environ.get(key, value) @@ -3691,8 +3697,12 @@ def setup_environment( value = old_val.union(value) value = ';'.join(item for item in value) + distutils.log.debug(key + '=' + value) os.environ[key] = value + distutils.log.debug('\n\n') + distutils.log.set_threshold(distutils.log.ERROR) + return environment From 938cc84e9dd4aad0ec4706a0c401a3102e8c0732 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Tue, 25 Jan 2022 22:35:00 -0700 Subject: [PATCH 12/23] fixes path in msvc environment --- buildtools/msvc/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index c4c8c5fb6a..f91e099414 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -3638,8 +3638,6 @@ def setup_environment( :rtype: Environment """ - distutils.log.set_threshold(distutils.log.DEBUG) - if not _IS_WIN: raise RuntimeError( 'This script will only work with a Windows opperating system.' @@ -3692,16 +3690,15 @@ def setup_environment( if old_val != value: if ';' in old_val or ';' in value: old_val = set(old_val.split(';')) - value = set(';'.split(value)) + value = set(value.split(';')) value = old_val.union(value) - value = ';'.join(item for item in value) + value = ';'.join(item.strip() for item in value if item.strip()) distutils.log.debug(key + '=' + value) os.environ[key] = value distutils.log.debug('\n\n') - distutils.log.set_threshold(distutils.log.ERROR) return environment From 868f158b0faf29cefc4ca0f71fdb42345328484b Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 00:41:33 -0700 Subject: [PATCH 13/23] Speeds up Windows build I am not 100% if this is going to work properly. It does compile correctly and it is substantially faster. for the wxWidgets compilation I use the msvc build system I added to check if cmake and ninja are available and if they are I use that if JOM is not being used. the --jobs command line switch sets the number of parallel files that can be compiled. Windows builds on Azure typically take around 45 minutes, we will see how much that gets cut down. It took 1 minutes 30 seconds to compile wxWidgets on my 6 core using 6 jobs. I don't remember off hand how long it took previously but I sure know it didn't take a minute and 30 seconds!! --- buildtools/build_wxwidgets.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index fe6defe105..9f05d9e300 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -478,12 +478,24 @@ def main(wxDir, args): "CPPFLAGS=/I%s" % os.path.join(os.environ.get("CAIRO_ROOT", ""), 'include\\cairo')) - if options.jom: - nmakeCommand = 'jom.exe' - if options.no_dpi_aware: args.append("USE_DPI_AWARE_MANIFEST=0") + if options.jom: + nmakeCommand = 'jom.exe' + else: + from . import msvc + environment = msvc.setup_environment(minimum_c_version=14.2) + + if ( + environment.visual_c.has_cmake and + environment.visual_c.has_ninja + ): + args.pop(0) + for i, arg in enumerate(args[:]): + args[i] = '-D' + arg + buildDir = wxRootDir + nmakeCommand = 'cmake.exe' wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) @@ -511,10 +523,18 @@ def main(wxDir, args): if options.extra_make: args.append(options.extra_make) - if not sys.platform.startswith("win"): + if sys.platform.startswith("win"): + if nmakeCommand.startswith('cmake'): + args.insert(0, '-GNinja') + else: args.append("--jobs=" + options.jobs) + exitIfError(wxBuilder.build(dir=buildDir, options=args), "Error building") + if sys.platform.startswith("win") and nmakeCommand.startswith('cmake'): + wxBuilder = builder.MSVCBuilder(commandName='ninja.exe') + exitIfError(wxBuilder.build(dir=buildDir, options=['-j ' + options.jobs]), "Error building") + if options.install: extra=None if installDir: From dc6edc1e95f8652530364eee444e75ba21edfebe Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 00:59:16 -0700 Subject: [PATCH 14/23] fixes msvc environment path so the already stet path is added to the end of the new path --- buildtools/msvc/__init__.py | 42 ++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py index f91e099414..d4af6c0dc9 100644 --- a/buildtools/msvc/__init__.py +++ b/buildtools/msvc/__init__.py @@ -3525,18 +3525,17 @@ def build_environment(self): __VSCMD_script_err_count='0' ) - path = set(item.strip() for item in path.split(';') if item.strip()) env_path = set() def update_env(cls): for key, value in cls: if key == 'Path': - for item in value.split(';'): + for item in value.split(os.pathsep): env_path.add(item) continue if key in env: - env[key] += ';' + value + env[key] += os.pathsep + value else: env[key] = value @@ -3545,13 +3544,8 @@ def update_env(cls): update_env(self.windows_sdk) update_env(self.dot_net) - env_path = [item for item in env_path if item not in path] - env_path = ';'.join(env_path) - - if env_path: - env_path += ';' - - env['Path'] = env_path + ';'.join(path) + env_path = os.pathsep.join(item for item in env_path) + env['Path'] = env_path return env @@ -3688,12 +3682,17 @@ def setup_environment( for key, value in environment.build_environment.items(): old_val = os.environ.get(key, value) if old_val != value: - if ';' in old_val or ';' in value: - old_val = set(old_val.split(';')) - value = set(value.split(';')) - - value = old_val.union(value) - value = ';'.join(item.strip() for item in value if item.strip()) + if os.pathsep in old_val or os.pathsep in value: + value = [item for item in set(value.split(os.pathsep))] + old_val = [ + item for item in set(old_val.split(os.pathsep)) + if item not in value + ] + + value.extend(old_val) + value = os.pathsep.join( + item.strip() for item in value if item.strip() + ) distutils.log.debug(key + '=' + value) os.environ[key] = value @@ -3715,11 +3714,6 @@ def setup_environment( print('SET ENVIRONMENT VARIABLES') print('------------------------------------------------') print() - for k, v in envr: - if os.pathsep in v: - v = v.split(';') - if not v[-1]: - v = v[:-1] - - print(k + ':', v) - print() + for k in envr.build_environment.keys(): + v = os.environ[k] + print(k + '=', v) From e88f17543b3a71d456af203e30ffd5f1e3305abc Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 01:34:38 -0700 Subject: [PATCH 15/23] See if this fixes the missing include folder. --- buildtools/build_wxwidgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index 9f05d9e300..781307ce54 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -496,6 +496,9 @@ def main(wxDir, args): args[i] = '-D' + arg buildDir = wxRootDir nmakeCommand = 'cmake.exe' + os.environ['INCLUDE'] += ( + ';' + os.path.join(wxRootDir, 'include', 'msvc') + ) wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) From c4c805b358057cf72e01ffbe62ecaed046f7cd02 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 02:17:36 -0700 Subject: [PATCH 16/23] sets the msvc environment into a global so it doesn't get run more then a single time. --- build.py | 90 +++++++++++++++++++---------------- buildtools/build_wxwidgets.py | 5 +- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/build.py b/build.py index 586a317227..18412f2a88 100755 --- a/build.py +++ b/build.py @@ -79,6 +79,7 @@ isWindows = sys.platform.startswith('win') isDarwin = sys.platform == "darwin" devMode = False +win_environment = None baseName = version.PROJECT_NAME eggInfoName = baseName + '.egg-info' @@ -785,47 +786,54 @@ def uploadTree(srcPath, destPath, options, days=30): def checkCompiler(quiet=False): if isWindows: - from buildtools import msvc - - # setup the build environment making the minimum conpiler version 14.2 - # A user does not need to have a full blown Visual Studio installation - # in order to build wxPython. They can use Visual Studio Build Tools - # as well. Build Tools doe not include any of the GUI replated - # components of Visual Studio. - # If the msvc script isunable to locate an MSVC compiler that is of - # version >= 14.2 the msvc script will raise RuntimeError. - - # the msvc script works in a manner where no part of either setuptools - # or distutils is overriden. It uses mechanics that can alter what - # setuptools and distutils sees and uses for the build environment - # without tampering with any of the original code in those libraries. - - # the msvc script does not use the problematic vcvars*.bat files that - # come with an MSVC compiler. It builds the environment from the ground - # up by reading the registry and using the COM interfaces. The - # subprocess module is only used for Visual Studio installatons that - # are older then 2017 which wouldn't be an issue because the wxPython - # build system needs the MSVC compiler to be at least 14.2 (VS 2019) - - # If the environment variable "DISTUTILS_DEBUG=1" is set There is - # going to be a HUGE dump of information from the msvc module to - # stdout by way of distutils.log.debug(). If the logging level is set - # to DEBUG by using distutils.log.set_threshold(distutils.log.DEBUG) - # or any other mechanics to set the logging level to DEBUG here will - # be a heap of information dumped to stdout by the msvc module. - - # Just so this is on record and can be used at a later date if needs - # be the MSVC compiler defaults to c++11. If you need to change this - # extra compiler arguments will have to be added, "/std:cpp_version" - # where cpp_version is the version of cpp to be used ie: "c++20". - # The __cplusplus macro does not change when using the /std compiler - # switch. You have to specifically tell the compuler to update the - # value for the __cplusplus macro using "/Zc:__cplusplus" - environment = msvc.setup_environment(minimum_c_version=14.2) - - if not quiet: - # this gives a simple output of the build environment. - print(environment) + global win_environment + + if win_environment is None: + from buildtools import msvc + + # setup the build environment making the minimum conpiler version + # 14.2 A user does not need to have a full blown Visual Studio + # installation in order to build wxPython. They can use Visual + # Studio Build Tools as well. Build Tools doe not include any of + # the GUI replated components of Visual Studio. + # If the msvc script isunable to locate an MSVC compiler that is + # of version >= 14.2 the msvc script will raise RuntimeError. + + # the msvc script works in a manner where no part of either + # setuptools or distutils is overriden. It uses mechanics that can + # alter what setuptools and distutils sees and uses for the build + # environment without tampering with any of the original code in + # those libraries. + + # the msvc script does not use the problematic vcvars*.bat files + # that come with an MSVC compiler. It builds the environment from + # the ground up by reading the registry and using the COM + # interfaces. The subprocess module is only used for Visual Studio + # installatons that are older then 2017 which wouldn't be an + # issue because the wxPython build system needs the MSVC compiler + # to be at least 14.2 (VS 2019) + + # If the environment variable "DISTUTILS_DEBUG=1" is set There is + # going to be a HUGE dump of information from the msvc module to + # stdout by way of distutils.log.debug(). If the logging level is + # set to DEBUG by using distutils.log.set_threshold( + # distutils.log.DEBUG) or any other mechanics to set the logging + # level to DEBUG here will be a heap of information dumped to + # stdout by the msvc module. + + # Just so this is on record and can be used at a later date if + # needs be the MSVC compiler defaults to c++11. If you need to + # change this extra compiler arguments will have to be added, + # "/std:cpp_version" where cpp_version is the version of cpp to + # be used ie: "c++20". The __cplusplus macro does not change when + # using the /std compiler switch. You have to specifically tell + # the compiler to update the value for the __cplusplus macro + # using "/Zc:__cplusplus" + win_environment = msvc.setup_environment(minimum_c_version=14.2) + + if not quiet: + # this gives a simple output of the build environment. + print(win_environment) # NOTE: SIP is now generating code with scoped-enums. Older linux # platforms like what we're using for builds, and also TravisCI for diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index 781307ce54..51d3dc54aa 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -496,8 +496,9 @@ def main(wxDir, args): args[i] = '-D' + arg buildDir = wxRootDir nmakeCommand = 'cmake.exe' - os.environ['INCLUDE'] += ( - ';' + os.path.join(wxRootDir, 'include', 'msvc') + os.environ['INCLUDE'] = ( + os.path.join(wxRootDir, 'include', 'msvc') + ';' + + os.environ['INCLUDE'] ) wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) From 519196e4f85084d288fb8fd580a5d8b53a6439ea Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 03:02:39 -0700 Subject: [PATCH 17/23] Hopefully I have it fixed now!! --- buildtools/build_wxwidgets.py | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index 51d3dc54aa..f8f758ad9f 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -491,15 +491,59 @@ def main(wxDir, args): environment.visual_c.has_cmake and environment.visual_c.has_ninja ): - args.pop(0) - for i, arg in enumerate(args[:]): - args[i] = '-D' + arg + buildDir = wxRootDir nmakeCommand = 'cmake.exe' - os.environ['INCLUDE'] = ( - os.path.join(wxRootDir, 'include', 'msvc') + ';' + - os.environ['INCLUDE'] + libdirname = ( + 'vc' + + environment.visual_c.version.split('.')[0] + + '0_' + + environment.python.architecture + + '_dll' + ) + libdirname = os.path.join(wxRootDir, 'lib', libdirname) + if not os.path.exists(libdirname): + os.makedirs(libdirname) + + setuphdir = os.path.join( + libdirname, + 'mswu' if 'UNICODE=1' in args else 'msw' ) + if not os.path.exists(setuphdir): + os.makedirs(setuphdir) + + setuphdirwx = os.path.join(setuphdir, 'wx') + if not os.path.exists(setuphdirwx): + os.makedirs(setuphdirwx) + + setuphdirwxsetup = os.path.join(setuphdirwx, 'setup.h') + if not os.path.exists(setuphdirwxsetup): + import shutil + + src = os.path.join(wxRootDir, 'include', 'wx', 'msw', 'setup.h') + shutil.copyfile(src, setuphdirwxsetup) + + setuphdirwxmsw = os.path.join(setuphdirwx, 'msw') + if not os.path.exists(setuphdirwxmsw): + os.makedirs(setuphdirwxmsw) + + setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') + if not os.path.exists(setuphdirwxmswrcdefs): + genrcdefs = os.path.join(wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h') + command = 'cl /EP /nologo "{0}" > "{0}"'.format( + genrcdefs, + setuphdirwxmswrcdefs + ) + run(command) + + if 'CPPFLAGS' not in os.environ: + os.environ['CPPFLAGS'] = '/I"{0}"'.format(setuphdir) + else: + os.environ['CPPFLAGS'] += ' /I"{0}"'.format(setuphdir) + + args.pop(0) + for i, arg in enumerate(args[:]): + args[i] = '-D' + arg wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) From ad4aa608832a5eb090f1e8a84f61b56fe44b5da1 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Wed, 26 Jan 2022 03:32:57 -0700 Subject: [PATCH 18/23] Fixes formatting error --- buildtools/build_wxwidgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index f8f758ad9f..5b53dd3d85 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -530,7 +530,7 @@ def main(wxDir, args): setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') if not os.path.exists(setuphdirwxmswrcdefs): genrcdefs = os.path.join(wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h') - command = 'cl /EP /nologo "{0}" > "{0}"'.format( + command = 'cl /EP /nologo "{0}" > "{1}"'.format( genrcdefs, setuphdirwxmswrcdefs ) From a61ef61e166ba6e189c73ab80dda364910fb81d6 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Sat, 29 Jan 2022 09:23:59 -0700 Subject: [PATCH 19/23] Fixes Windows ninja compilation Thanks goes out to @oleksis for pointing me in the right direction. I have also started to remove any use of distutils for handling anything with the msvc compiler as I had found that it is mucking up the build environment --- build.py | 47 ++++- buildtools/build_wxwidgets.py | 322 +++++++++++++++++++++++----------- buildtools/config.py | 29 ++- wscript | 43 +++-- 4 files changed, 311 insertions(+), 130 deletions(-) diff --git a/build.py b/build.py index 18412f2a88..7c9db076b1 100755 --- a/build.py +++ b/build.py @@ -1695,7 +1695,6 @@ def _getWxCompiler(flag, compName, flagName): _getWxCompiler('--cc', 'CC', 'CFLAGS') _getWxCompiler('--cxx', 'CXX', 'CXXFLAGS') - wafBuildBase = wafBuildDir = getWafBuildBase() if isWindows: wafBuildDir = posixjoin(wafBuildBase, 'release') @@ -1986,9 +1985,55 @@ def cmd_egg_info(options, args, egg_base=None): def cmd_clean_wx(options, args): cmdTimer = CommandTimer('clean_wx') if isWindows: + checkCompiler() + if ( + win_environment.visual_c.has_cmake and + win_environment.visual_c.has_ninja + ): + wx_dir = wxDir() + + if win_environment.python.architecture == 'x64': + libdirname = 'vc_x64_dll' + libdirrename = 'vc%s_x64_dll' + else: + libdirname = 'vc_dll' + libdirrename = 'vc%s_dll' + + libdirrename %= getVisCVersion() + + files = [ + '.ninja_deps', + '.ninja_log', + 'cmake_install.cmake', + 'uninstall.cmake', + 'CMakeCache.txt', + 'CMakeFiles', + 'libs', + 'utils/CMakeFiles', + 'utils/cmake_install.cmake', + 'lib/' + libdirrename, + 'lib/' + libdirname + ] + + for f in files: + f = os.path.join(wx_dir, f) + if not os.path.exists(f): + continue + if os.path.isdir(f): + try: + shutil.rmtree(f) + except OSError: + pass + elif os.path.isfile(f): + try: + os.remove(f) + except OSError: + pass + if options.both: options.debug = True msw = getMSWSettings(options) + deleteIfExists(opj(msw.dllDir, 'msw'+msw.dll_type)) delFiles(glob.glob(opj(msw.dllDir, 'wx*%s%s*' % (wxversion2_nodot, msw.dll_type)))) delFiles(glob.glob(opj(msw.dllDir, 'wx*%s%s*' % (wxversion3_nodot, msw.dll_type)))) diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index 5b53dd3d85..fa060df9b9 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -63,19 +63,17 @@ def getXcodePaths(): return [base, base+"/Platforms/MacOSX.platform/Developer"] +win_environment = None + def getVisCVersion(): - text = getoutput("cl.exe") - if 'Version 13' in text: - return '71' - if 'Version 15' in text: - return '90' - if 'Version 16' in text: - return '100' - if 'Version 19' in text: - return '140' - # TODO: Add more tests to get the other versions... - else: - return 'FIXME' + global win_environment + + if win_environment is None: + from . import msvc + + win_environment = msvc.setup_environment(minimum_c_version=14.2) + + return win_environment.visual_c.version.split('.')[0] + '0' def exitIfError(code, msg): @@ -429,7 +427,6 @@ def main(wxDir, args): if not options.no_msedge: flags["wxUSE_WEBVIEW_EDGE"] = "1" - mswIncludeDir = os.path.join(wxRootDir, "include", "wx", "msw") setupFile = os.path.join(mswIncludeDir, "setup.h") with open(setupFile, "rb") as f: @@ -451,99 +448,167 @@ def main(wxDir, args): args = [] if toolkit == "msvc": print("setting build options...") - args.append("-f makefile.vc") - if options.unicode: - args.append("UNICODE=1") - if VERSION < (2,9): - args.append("MSLU=1") - - if options.wxpython: - args.append("OFFICIAL_BUILD=1") - args.append("COMPILER_VERSION=%s" % getVisCVersion()) - args.append("SHARED=1") - args.append("MONOLITHIC=0") - args.append("USE_OPENGL=1") - args.append("USE_GDIPLUS=1") - - if not options.debug: - args.append("BUILD=release") + from . import msvc + environment = msvc.setup_environment(minimum_c_version=14.2) + if ( + not options.jom and + environment.visual_c.has_cmake and + environment.visual_c.has_ninja + ): + nmakeCommand = 'cmake.exe' + if environment.python.architecture == 'x64': + libdirname = 'vc_x64_dll' + libdirrename = 'vc%s_x64_dll' else: - args.append("BUILD=debug") + libdirname = 'vc_dll' + libdirrename = 'vc%s_dll' - if options.shared: - args.append("SHARED=1") + libdirrename %= getVisCVersion() + libdirrename = os.path.join(wxRootDir, 'lib', libdirrename) + libdirname = os.path.join(wxRootDir, 'lib', libdirname) - if options.cairo: - args.append( - "CPPFLAGS=/I%s" % - os.path.join(os.environ.get("CAIRO_ROOT", ""), 'include\\cairo')) + if not os.path.exists(libdirname): + print('creating directory', libdirname) + os.makedirs(libdirname) - if options.no_dpi_aware: - args.append("USE_DPI_AWARE_MANIFEST=0") - - if options.jom: - nmakeCommand = 'jom.exe' - else: - from . import msvc - environment = msvc.setup_environment(minimum_c_version=14.2) - - if ( - environment.visual_c.has_cmake and - environment.visual_c.has_ninja - ): - - buildDir = wxRootDir - nmakeCommand = 'cmake.exe' - libdirname = ( - 'vc' + - environment.visual_c.version.split('.')[0] + - '0_' + - environment.python.architecture + - '_dll' + if options.unicode: + setuphdir = os.path.join(libdirname, 'mswu') + else: + setuphdir = os.path.join(libdirname, 'msw') + + if not os.path.exists(setuphdir): + print('creating directory', setuphdir) + os.makedirs(setuphdir) + + setuphdirwx = os.path.join(setuphdir, 'wx') + if not os.path.exists(setuphdirwx): + print('creating directory', setuphdirwx) + os.makedirs(setuphdirwx) + + setuphdirwxsetup = os.path.join(setuphdirwx, 'setup.h') + if not os.path.exists(setuphdirwxsetup): + src = os.path.join( + wxRootDir, + 'include', + 'wx', + 'msw', + 'setup.h' + ) + print('copying file', src, '->', setuphdirwxsetup) + shutil.copyfile(src, setuphdirwxsetup) + + setuphdirwxmsw = os.path.join(setuphdirwx, 'msw') + if not os.path.exists(setuphdirwxmsw): + print('creating directory', setuphdirwxmsw) + os.makedirs(setuphdirwxmsw) + + setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') + if not os.path.exists(setuphdirwxmswrcdefs): + genrcdefs = os.path.join( + wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h' + ) + command = 'cl /EP /nologo "{0}" > "{1}"'.format( + genrcdefs, + setuphdirwxmswrcdefs ) - libdirname = os.path.join(wxRootDir, 'lib', libdirname) - if not os.path.exists(libdirname): - os.makedirs(libdirname) - setuphdir = os.path.join( - libdirname, - 'mswu' if 'UNICODE=1' in args else 'msw' + print( + 'compiling file', + genrcdefs, + '->', + setuphdirwxmswrcdefs ) - if not os.path.exists(setuphdir): - os.makedirs(setuphdir) - - setuphdirwx = os.path.join(setuphdir, 'wx') - if not os.path.exists(setuphdirwx): - os.makedirs(setuphdirwx) - - setuphdirwxsetup = os.path.join(setuphdirwx, 'setup.h') - if not os.path.exists(setuphdirwxsetup): - import shutil - - src = os.path.join(wxRootDir, 'include', 'wx', 'msw', 'setup.h') - shutil.copyfile(src, setuphdirwxsetup) - - setuphdirwxmsw = os.path.join(setuphdirwx, 'msw') - if not os.path.exists(setuphdirwxmsw): - os.makedirs(setuphdirwxmsw) - - setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') - if not os.path.exists(setuphdirwxmswrcdefs): - genrcdefs = os.path.join(wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h') - command = 'cl /EP /nologo "{0}" > "{1}"'.format( - genrcdefs, - setuphdirwxmswrcdefs + run(command) + + if 'CPPFLAGS' not in os.environ: + os.environ['CPPFLAGS'] = '/I"{0}"'.format(setuphdir) + else: + os.environ['CPPFLAGS'] += ' /I"{0}"'.format(setuphdir) + + args.append("-DwxBUILD_CUSTOM_SETUP_HEADER_PATH=%s" % setuphdir) + + if options.wxpython: + # args.append("-DOFFICIAL_BUILD=1") + args.append("-DCOMPILER_VERSION=%s" % getVisCVersion()) + # args.append("-DwxBUILD_VENDOR=wxPython") + # args.append("-DwxBUILD_FLAVOUR=wxPython") + args.append("-DwxBUILD_SHARED=1") + args.append("-DwxBUILD_MONOLITHIC=0") + args.append("-DwxUSE_OPENGL=1") + args.append("-DwxUSE_GRAPHICS_GDIPLUS=1") + + if options.debug: + args.append("-DCMAKE_BUILD_TYPE=Debug") + args.append("-DwxBUILD_OPTIMISE=0") + args.append("-DwxBUILD_STRIPPED_RELEASE=0") + + else: + args.append("-DCMAKE_BUILD_TYPE=Release") + args.append("-DwxBUILD_OPTIMISE=1") + args.append("-DwxBUILD_STRIPPED_RELEASE=1") + + if options.shared: + args.append("-DwxBUILD_SHARED=1") + + if options.cairo: + args.append( + "CMAKE_CXX_FLAGS=/DWIN32 /D_WINDOWS /GR /EHsc /I%s" % + os.path.join( + os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' ) - run(command) + ) + + if options.no_dpi_aware: + args.append("-DwxUSE_DPI_AWARE_MANIFEST=none") + + for flag, value in flags.items(): + if flag in ( + 'wxDIALOG_UNIT_COMPATIBILITY', + 'wxUSE_DATEPICKCTRL_GENERIC' + ): + continue + + args.append('-D' + flag.strip() + '=' + value) - if 'CPPFLAGS' not in os.environ: - os.environ['CPPFLAGS'] = '/I"{0}"'.format(setuphdir) + args.append('-S"%s"' % wxRootDir) + args.append('-B"%s"' % wxRootDir) + + else: + if options.jom: + nmakeCommand = 'jom.exe' + + args.append("-f makefile.vc") + if options.unicode: + args.append("UNICODE=1") + if VERSION < (2, 9): + args.append("MSLU=1") + + if options.wxpython: + args.append("OFFICIAL_BUILD=1") + args.append("COMPILER_VERSION=%s" % getVisCVersion()) + args.append("SHARED=1") + args.append("MONOLITHIC=0") + args.append("USE_OPENGL=1") + args.append("USE_GDIPLUS=1") + + if not options.debug: + args.append("BUILD=release") else: - os.environ['CPPFLAGS'] += ' /I"{0}"'.format(setuphdir) + args.append("BUILD=debug") - args.pop(0) - for i, arg in enumerate(args[:]): - args[i] = '-D' + arg + if options.shared: + args.append("SHARED=1") + + if options.cairo: + args.append( + "CPPFLAGS=/I%s" % + os.path.join( + os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' + ) + ) + + if options.no_dpi_aware: + args.append("USE_DPI_AWARE_MANIFEST=0") wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) @@ -564,7 +629,48 @@ def main(wxDir, args): if options.clean: print("Performing cleanup.") - wxBuilder.clean(dir=buildDir, options=args) + if ( + sys.platform.startswith("win") and + nmakeCommand.startswith('cmake') + ): + try: + shutil.rmtree(libdirrename) + except OSError: + pass + + try: + shutil.rmtree(libdirname) + except OSError: + pass + + files = [ + '.ninja_deps', + '.ninja_log', + 'cmake_install.cmake', + 'uninstall.cmake', + 'CMakeCache.txt', + 'CMakeFiles', + 'libs', + 'utils/CMakeFiles', + 'utils/cmake_install.cmake', + ] + + for f in files: + f = os.path.join(wxRootDir, f) + if not os.path.exists(f): + continue + if os.path.isdir(f): + try: + shutil.rmtree(f) + except OSError: + pass + elif os.path.isfile(f): + try: + os.remove(f) + except OSError: + pass + else: + wxBuilder.clean(dir=buildDir, options=args) sys.exit(0) @@ -580,8 +686,28 @@ def main(wxDir, args): exitIfError(wxBuilder.build(dir=buildDir, options=args), "Error building") if sys.platform.startswith("win") and nmakeCommand.startswith('cmake'): - wxBuilder = builder.MSVCBuilder(commandName='ninja.exe') - exitIfError(wxBuilder.build(dir=buildDir, options=['-j ' + options.jobs]), "Error building") + ninja_builder = builder.MSVCBuilder(commandName='ninja.exe') + for arg in args: + if arg.startswith('-B'): + break + else: + raise RuntimeError('Sanity check, this shouldn\'t happen') + ninja_args = [ + '-C"{0}"'.format(wxRootDir), + '-f"{0}"'.format(os.path.join(wxRootDir, 'build.ninja')), + '-j ' + options.jobs + ] + exitIfError(ninja_builder.build(dir=buildDir, options=ninja_args), "Error building") + print('renaming directory', libdirname, '->', libdirrename) + os.rename(libdirname, libdirrename) + for f in os.listdir(libdirrename): + if 'vc_custom' in f: + dst = f.replace('vc_custom', 'vc' + getVisCVersion()) + print('renaming file', f, '->', dst) + + dst = os.path.join(libdirrename, dst) + src = os.path.join(libdirrename, f) + os.rename(src, dst) if options.install: extra=None diff --git a/buildtools/config.py b/buildtools/config.py index fb707c7d6c..71f915c9ee 100644 --- a/buildtools/config.py +++ b/buildtools/config.py @@ -157,8 +157,15 @@ def finishSetup(self, wx_config=None, debug=None): if os.environ.get('CPU', None) in ['AMD64', 'X64']: self.VCDLL = 'vc%s_x64_dll' % getVisCVersion() + # Fix error RC2188 ..\ext\wxWidgets\include\wx/msw/wx.rc(125) + self.defines = [ + ('WX_CPU_AMD64', None), + ] else: self.VCDLL = 'vc%s_dll' % getVisCVersion() + self.defines = [ + ('WX_CPU_X86', None), + ] self.includes += ['include', opj(self.WXDIR, 'lib', self.VCDLL, 'msw' + self.libFlag()), @@ -166,7 +173,7 @@ def finishSetup(self, wx_config=None, debug=None): opj(self.WXDIR, 'contrib', 'include'), ] - self.defines = [ ('WIN32', None), + self.defines += [ ('WIN32', None), ('_WINDOWS', None), (self.WXPLAT, None), ('WXUSINGDLL', '1'), @@ -178,7 +185,7 @@ def finishSetup(self, wx_config=None, debug=None): ('wxUSE_DPI_AWARE_MANIFEST', '2') ] self.libs = [] - self.libdirs = [ opj(self.WXDIR, 'lib', self.VCDLL) ] + self.libdirs = [opj(self.WXDIR, 'lib', self.VCDLL)] if self.MONOLITHIC: self.libs += makeLibName('') else: @@ -951,20 +958,10 @@ def getSipFiles(names): def getVisCVersion(): - text = runcmd("cl.exe", getOutput=True, echoCmd=False) - if 'Version 13' in text: - return '71' - if 'Version 15' in text: - return '90' - if 'Version 16' in text: - return '100' - if 'Version 18' in text: - return '120' - if 'Version 19' in text: - return '140' - # TODO: Add more tests to get the other versions... - else: - return 'FIXME' + from . import msvc + environment = msvc.setup_environment(minimum_c_version=14.2) + version = environment.visual_c.version + return version.split('.')[0] + '0' _haveObjDump = None diff --git a/wscript b/wscript index 8b547a2ab3..380df44d7d 100644 --- a/wscript +++ b/wscript @@ -70,8 +70,10 @@ def configure(conf): # version. We have a chicken-egg problem here. The compiler needs to # be selected before the Python stuff can be configured, but we need # Python to know what version of the compiler to use. - import distutils.msvc9compiler - msvc_version = str( distutils.msvc9compiler.get_build_version() ) + from buildtools import msvc + + environment = msvc.setup_environment(minimum_c_version=14.2) + msvc_version = environment.visual_c.version # When building for Python 3.7 the msvc_version returned will be # "14.1" as that is the version of the BasePlatformToolkit that stock @@ -416,11 +418,13 @@ def my_check_python_headers(conf): conf.parse_flags(all_flags, 'PYEXT') if isWindows: - libname = 'python' + conf.env['PYTHON_VERSION'].replace('.', '') + from buildtools import msvc - if dct['LIBDIR'] and os.path.isdir(dct['LIBDIR']): - libpath = [dct['LIBDIR']] - else: + python_info = msvc.PythonInfo() + libname = os.path.splitext(python_info.dependency)[0] + libpath = python_info.libraries + + if not libpath or not os.path.isdir(libpath[0]): base_prefix = get_windows_base_prefix(conf, dct['prefix']) libpath = [os.path.join(base_prefix, "libs")] @@ -478,12 +482,22 @@ def my_check_python_headers(conf): env.append_value('CXXFLAGS_PYEXT', ['-fno-strict-aliasing']) if env.CC_NAME == "msvc": - from distutils.msvccompiler import MSVCCompiler - dist_compiler = MSVCCompiler() - dist_compiler.initialize() - env.append_value('CFLAGS_PYEXT', dist_compiler.compile_options) - env.append_value('CXXFLAGS_PYEXT', dist_compiler.compile_options) - env.append_value('LINKFLAGS_PYEXT', dist_compiler.ldflags_shared) + from buildtools import msvc + environment = msvc.setup_environment(minimum_c_version=14.2) + + ldflags_shared = [ + '/DLL', '/nologo', '/INCREMENTAL:NO', '/LTCG' + ] + compile_options = ['/nologo', '/Ox', '/GL', '/MD', '/W3', '/DNDEBUG'] + + if environment.python.architecture == 'x86': + compile_options += ['/GX'] + else: + compile_options += ['/GS-'] + + env.append_value('CFLAGS_PYEXT', compile_options) + env.append_value('CXXFLAGS_PYEXT', compile_options) + env.append_value('LINKFLAGS_PYEXT', ldflags_shared) def get_windows_base_prefix(conf, default): @@ -591,7 +605,6 @@ def build(bld): uselib = 'siplib WX WXPY', ) makeExtCopyRule(bld, 'siplib') - # Add build rules for each of our ETG generated extension modules makeETGRule(bld, 'etg/_core.py', '_core', 'WX') makeETGRule(bld, 'etg/_adv.py', '_adv', 'WXADV') @@ -608,7 +621,6 @@ def build(bld): makeETGRule(bld, 'etg/_ribbon.py', '_ribbon', 'WXRIBBON') makeETGRule(bld, 'etg/_propgrid.py', '_propgrid', 'WXPROPGRID') makeETGRule(bld, 'etg/_aui.py', '_aui', 'WXAUI') - # Modules that are platform-specific if isWindows: makeETGRule(bld, 'etg/_msw.py', '_msw', 'WX') @@ -687,7 +699,7 @@ def _copyEnvGroup(env, srcPostfix, destPostfix): # Make extension module build rules using info gleaned from an ETG script def makeETGRule(bld, etgScript, moduleName, libFlags): - from buildtools.config import loadETG, getEtgSipCppFiles + from buildtools.config import loadETG, getEtgSipCppFiles addRelwithdebugFlags(bld, moduleName) @@ -701,6 +713,7 @@ def makeETGRule(bld, etgScript, moduleName, libFlags): ) rc = [rc_name] + etg = loadETG(etgScript) bld(features='c cxx cxxshlib pyext', target=makeTargetName(bld, moduleName), From 0be530144f0f0a24165fd04e8111338411a7d6d8 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 1 Aug 2022 02:32:10 -0600 Subject: [PATCH 20/23] Fixes issue with WSL and removes use of CMAKE and Ninja --- build.py | 139 +++++------ buildtools/build_wxwidgets.py | 443 +++++++++++++++++----------------- buildtools/config.py | 3 + 3 files changed, 295 insertions(+), 290 deletions(-) diff --git a/build.py b/build.py index 7c9db076b1..6decdae502 100755 --- a/build.py +++ b/build.py @@ -907,19 +907,21 @@ def dos2bashPath(path): # Note that MSYS2 (and Git Bash) now also have cygpath so this should # work there too. if cygpath: - path = runcmd('"{}" -u "{}"'.format(cygpath, path), getOutput=True, echoCmd=False) - return path - elif wsl: + pth = runcmd('"{}" -u "{}"'.format(cygpath, path), getOutput=True, echoCmd=False, fatal=False) + return pth + if wsl: # Are we using Windows System for Linux? (untested) - path = runcmd('"{}" wslpath -a -u "{}"'.format(wsl, path), getOutput=True, echoCmd=False) - return path - else: - # Otherwise, do a simple translate and hope for the best? - # c:/foo --> /c/foo - # TODO: Check this!! - drive, rest = os.path.splitdrive(path) - path = '/{}/{}'.format(drive[0], rest) - return path + pth = runcmd('"{}" wslpath -a -u "{}"'.format(wsl, path), getOutput=True, echoCmd=False, fatal=False) + + if 'Usage: wsl.exe' not in pth: + return pth + + # Otherwise, do a simple translate and hope for the best? + # c:/foo --> /c/foo + # TODO: Check this!! + drive, rest = os.path.splitdrive(path) + path = '/{}/{}'.format(drive[0], rest) + return path def bash2dosPath(path): @@ -933,21 +935,22 @@ def bash2dosPath(path): # Note that MSYS2 (and Git Bash) now also have cygpath so this should # work there too. if cygpath: - path = runcmd('"{}" -w "{}"'.format(cygpath, path), getOutput=True, echoCmd=False) + path = runcmd('"{}" -w "{}"'.format(cygpath, path), getOutput=True, echoCmd=False, fatal=False) return path - elif wsl: + if wsl: # Are we using Windows System for Linux? (untested) - path = runcmd('"{}" wslpath -a -w "{}"'.format(wsl, path), getOutput=True, echoCmd=False) - return path - else: - # Otherwise, do a simple translate and hope for the best? - # /c/foo --> c:/foo - # There's also paths like /cygdrive/c/foo or /mnt/c/foo, but in those - # cases cygpath or wsl should be available. - components = path.split('/') - assert components[0] == '' and len(components[1]) == 1, "Expecting a path like /c/foo" - path = components[1] + ':/' + '/'.join(components[2:]) - return path + path = runcmd('"{}" wslpath -a -w "{}"'.format(wsl, path), getOutput=True, echoCmd=False, fatal=False) + if 'Usage: wsl.exe' not in path: + return path + + # Otherwise, do a simple translate and hope for the best? + # /c/foo --> c:/foo + # There's also paths like /cygdrive/c/foo or /mnt/c/foo, but in those + # cases cygpath or wsl should be available. + components = path.split('/') + assert components[0] == '' and len(components[1]) == 1, "Expecting a path like /c/foo" + path = components[1] + ':/' + '/'.join(components[2:]) + return path def do_regenerate_sysconfig(): @@ -1986,49 +1989,49 @@ def cmd_clean_wx(options, args): cmdTimer = CommandTimer('clean_wx') if isWindows: checkCompiler() - if ( - win_environment.visual_c.has_cmake and - win_environment.visual_c.has_ninja - ): - wx_dir = wxDir() - - if win_environment.python.architecture == 'x64': - libdirname = 'vc_x64_dll' - libdirrename = 'vc%s_x64_dll' - else: - libdirname = 'vc_dll' - libdirrename = 'vc%s_dll' - - libdirrename %= getVisCVersion() - - files = [ - '.ninja_deps', - '.ninja_log', - 'cmake_install.cmake', - 'uninstall.cmake', - 'CMakeCache.txt', - 'CMakeFiles', - 'libs', - 'utils/CMakeFiles', - 'utils/cmake_install.cmake', - 'lib/' + libdirrename, - 'lib/' + libdirname - ] - - for f in files: - f = os.path.join(wx_dir, f) - if not os.path.exists(f): - continue - if os.path.isdir(f): - try: - shutil.rmtree(f) - except OSError: - pass - elif os.path.isfile(f): - try: - os.remove(f) - except OSError: - pass + # if ( + # win_environment.visual_c.has_cmake and + # win_environment.visual_c.has_ninja + # ): + # wx_dir = wxDir() + # + # if win_environment.python.architecture == 'x64': + # libdirname = 'vc_x64_dll' + # libdirrename = 'vc%s_x64_dll' + # else: + # libdirname = 'vc_dll' + # libdirrename = 'vc%s_dll' + # + # libdirrename %= getVisCVersion() + # + # files = [ + # '.ninja_deps', + # '.ninja_log', + # 'cmake_install.cmake', + # 'uninstall.cmake', + # 'CMakeCache.txt', + # 'CMakeFiles', + # 'libs', + # 'utils/CMakeFiles', + # 'utils/cmake_install.cmake', + # 'lib/' + libdirrename, + # 'lib/' + libdirname + # ] + # + # for f in files: + # f = os.path.join(wx_dir, f) + # if not os.path.exists(f): + # continue + # if os.path.isdir(f): + # try: + # shutil.rmtree(f) + # except OSError: + # pass + # elif os.path.isfile(f): + # try: + # os.remove(f) + # except OSError: + # pass if options.both: options.debug = True diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index fa060df9b9..b55ad89510 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -450,165 +450,164 @@ def main(wxDir, args): print("setting build options...") from . import msvc environment = msvc.setup_environment(minimum_c_version=14.2) - if ( - not options.jom and - environment.visual_c.has_cmake and - environment.visual_c.has_ninja - ): - nmakeCommand = 'cmake.exe' - if environment.python.architecture == 'x64': - libdirname = 'vc_x64_dll' - libdirrename = 'vc%s_x64_dll' + # if ( + # not options.jom and + # environment.visual_c.has_cmake and + # environment.visual_c.has_ninja + # ): + # nmakeCommand = 'cmake.exe' + # if environment.python.architecture == 'x64': + # libdirname = 'vc_x64_dll' + # libdirrename = 'vc%s_x64_dll' + # else: + # libdirname = 'vc_dll' + # libdirrename = 'vc%s_dll' + # + # libdirrename %= getVisCVersion() + # libdirrename = os.path.join(wxRootDir, 'lib', libdirrename) + # libdirname = os.path.join(wxRootDir, 'lib', libdirname) + # + # if not os.path.exists(libdirname): + # print('creating directory', libdirname) + # os.makedirs(libdirname) + # + # if options.unicode: + # setuphdir = os.path.join(libdirname, 'mswu') + # else: + # setuphdir = os.path.join(libdirname, 'msw') + # + # if not os.path.exists(setuphdir): + # print('creating directory', setuphdir) + # os.makedirs(setuphdir) + # + # setuphdirwx = os.path.join(setuphdir, 'wx') + # if not os.path.exists(setuphdirwx): + # print('creating directory', setuphdirwx) + # os.makedirs(setuphdirwx) + # + # setuphdirwxsetup = os.path.join(setuphdirwx, 'setup.h') + # if not os.path.exists(setuphdirwxsetup): + # src = os.path.join( + # wxRootDir, + # 'include', + # 'wx', + # 'msw', + # 'setup.h' + # ) + # print('copying file', src, '->', setuphdirwxsetup) + # shutil.copyfile(src, setuphdirwxsetup) + # + # setuphdirwxmsw = os.path.join(setuphdirwx, 'msw') + # if not os.path.exists(setuphdirwxmsw): + # print('creating directory', setuphdirwxmsw) + # os.makedirs(setuphdirwxmsw) + # + # setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') + # if not os.path.exists(setuphdirwxmswrcdefs): + # genrcdefs = os.path.join( + # wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h' + # ) + # command = 'cl /EP /nologo "{0}" > "{1}"'.format( + # genrcdefs, + # setuphdirwxmswrcdefs + # ) + # + # print( + # 'compiling file', + # genrcdefs, + # '->', + # setuphdirwxmswrcdefs + # ) + # run(command) + # + # if 'CPPFLAGS' not in os.environ: + # os.environ['CPPFLAGS'] = '/I"{0}"'.format(setuphdir) + # else: + # os.environ['CPPFLAGS'] += ' /I"{0}"'.format(setuphdir) + # + # args.append("-DwxBUILD_CUSTOM_SETUP_HEADER_PATH=%s" % setuphdir) + # + # if options.wxpython: + # # args.append("-DOFFICIAL_BUILD=1") + # args.append("-DCOMPILER_VERSION=%s" % getVisCVersion()) + # # args.append("-DwxBUILD_VENDOR=wxPython") + # # args.append("-DwxBUILD_FLAVOUR=wxPython") + # args.append("-DwxBUILD_SHARED=1") + # args.append("-DwxBUILD_MONOLITHIC=0") + # args.append("-DwxUSE_OPENGL=1") + # args.append("-DwxUSE_GRAPHICS_GDIPLUS=1") + # + # if options.debug: + # args.append("-DCMAKE_BUILD_TYPE=Debug") + # args.append("-DwxBUILD_OPTIMISE=0") + # args.append("-DwxBUILD_STRIPPED_RELEASE=0") + # + # else: + # args.append("-DCMAKE_BUILD_TYPE=Release") + # args.append("-DwxBUILD_OPTIMISE=1") + # args.append("-DwxBUILD_STRIPPED_RELEASE=1") + # + # if options.shared: + # args.append("-DwxBUILD_SHARED=1") + # + # if options.cairo: + # args.append( + # "CMAKE_CXX_FLAGS=/DWIN32 /D_WINDOWS /GR /EHsc /I%s" % + # os.path.join( + # os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' + # ) + # ) + # + # if options.no_dpi_aware: + # args.append("-DwxUSE_DPI_AWARE_MANIFEST=none") + # + # for flag, value in flags.items(): + # if flag in ( + # 'wxDIALOG_UNIT_COMPATIBILITY', + # 'wxUSE_DATEPICKCTRL_GENERIC' + # ): + # continue + # + # args.append('-D' + flag.strip() + '=' + value) + # + # args.append('-S"%s"' % wxRootDir) + # args.append('-B"%s"' % wxRootDir) + + if options.jom: + nmakeCommand = 'jom.exe' + + args.append("-f makefile.vc") + if options.unicode: + args.append("UNICODE=1") + if VERSION < (2, 9): + args.append("MSLU=1") + + if options.wxpython: + args.append("OFFICIAL_BUILD=1") + args.append("COMPILER_VERSION=%s" % getVisCVersion()) + args.append("SHARED=1") + args.append("MONOLITHIC=0") + args.append("USE_OPENGL=1") + args.append("USE_GDIPLUS=1") + + if not options.debug: + args.append("BUILD=release") else: - libdirname = 'vc_dll' - libdirrename = 'vc%s_dll' + args.append("BUILD=debug") - libdirrename %= getVisCVersion() - libdirrename = os.path.join(wxRootDir, 'lib', libdirrename) - libdirname = os.path.join(wxRootDir, 'lib', libdirname) + if options.shared: + args.append("SHARED=1") - if not os.path.exists(libdirname): - print('creating directory', libdirname) - os.makedirs(libdirname) - - if options.unicode: - setuphdir = os.path.join(libdirname, 'mswu') - else: - setuphdir = os.path.join(libdirname, 'msw') - - if not os.path.exists(setuphdir): - print('creating directory', setuphdir) - os.makedirs(setuphdir) - - setuphdirwx = os.path.join(setuphdir, 'wx') - if not os.path.exists(setuphdirwx): - print('creating directory', setuphdirwx) - os.makedirs(setuphdirwx) - - setuphdirwxsetup = os.path.join(setuphdirwx, 'setup.h') - if not os.path.exists(setuphdirwxsetup): - src = os.path.join( - wxRootDir, - 'include', - 'wx', - 'msw', - 'setup.h' - ) - print('copying file', src, '->', setuphdirwxsetup) - shutil.copyfile(src, setuphdirwxsetup) - - setuphdirwxmsw = os.path.join(setuphdirwx, 'msw') - if not os.path.exists(setuphdirwxmsw): - print('creating directory', setuphdirwxmsw) - os.makedirs(setuphdirwxmsw) - - setuphdirwxmswrcdefs = os.path.join(setuphdirwxmsw, 'cdefs.h') - if not os.path.exists(setuphdirwxmswrcdefs): - genrcdefs = os.path.join( - wxRootDir, 'include', 'wx', 'msw', 'genrcdefs.h' + if options.cairo: + args.append( + "CPPFLAGS=/I%s" % + os.path.join( + os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' ) - command = 'cl /EP /nologo "{0}" > "{1}"'.format( - genrcdefs, - setuphdirwxmswrcdefs - ) - - print( - 'compiling file', - genrcdefs, - '->', - setuphdirwxmswrcdefs - ) - run(command) - - if 'CPPFLAGS' not in os.environ: - os.environ['CPPFLAGS'] = '/I"{0}"'.format(setuphdir) - else: - os.environ['CPPFLAGS'] += ' /I"{0}"'.format(setuphdir) - - args.append("-DwxBUILD_CUSTOM_SETUP_HEADER_PATH=%s" % setuphdir) - - if options.wxpython: - # args.append("-DOFFICIAL_BUILD=1") - args.append("-DCOMPILER_VERSION=%s" % getVisCVersion()) - # args.append("-DwxBUILD_VENDOR=wxPython") - # args.append("-DwxBUILD_FLAVOUR=wxPython") - args.append("-DwxBUILD_SHARED=1") - args.append("-DwxBUILD_MONOLITHIC=0") - args.append("-DwxUSE_OPENGL=1") - args.append("-DwxUSE_GRAPHICS_GDIPLUS=1") - - if options.debug: - args.append("-DCMAKE_BUILD_TYPE=Debug") - args.append("-DwxBUILD_OPTIMISE=0") - args.append("-DwxBUILD_STRIPPED_RELEASE=0") - - else: - args.append("-DCMAKE_BUILD_TYPE=Release") - args.append("-DwxBUILD_OPTIMISE=1") - args.append("-DwxBUILD_STRIPPED_RELEASE=1") - - if options.shared: - args.append("-DwxBUILD_SHARED=1") - - if options.cairo: - args.append( - "CMAKE_CXX_FLAGS=/DWIN32 /D_WINDOWS /GR /EHsc /I%s" % - os.path.join( - os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' - ) - ) - - if options.no_dpi_aware: - args.append("-DwxUSE_DPI_AWARE_MANIFEST=none") - - for flag, value in flags.items(): - if flag in ( - 'wxDIALOG_UNIT_COMPATIBILITY', - 'wxUSE_DATEPICKCTRL_GENERIC' - ): - continue + ) - args.append('-D' + flag.strip() + '=' + value) - - args.append('-S"%s"' % wxRootDir) - args.append('-B"%s"' % wxRootDir) - - else: - if options.jom: - nmakeCommand = 'jom.exe' - - args.append("-f makefile.vc") - if options.unicode: - args.append("UNICODE=1") - if VERSION < (2, 9): - args.append("MSLU=1") - - if options.wxpython: - args.append("OFFICIAL_BUILD=1") - args.append("COMPILER_VERSION=%s" % getVisCVersion()) - args.append("SHARED=1") - args.append("MONOLITHIC=0") - args.append("USE_OPENGL=1") - args.append("USE_GDIPLUS=1") - - if not options.debug: - args.append("BUILD=release") - else: - args.append("BUILD=debug") - - if options.shared: - args.append("SHARED=1") - - if options.cairo: - args.append( - "CPPFLAGS=/I%s" % - os.path.join( - os.environ.get("CAIRO_ROOT", ""), 'include\\cairo' - ) - ) - - if options.no_dpi_aware: - args.append("USE_DPI_AWARE_MANIFEST=0") + if options.no_dpi_aware: + args.append("USE_DPI_AWARE_MANIFEST=0") wxBuilder = builder.MSVCBuilder(commandName=nmakeCommand) @@ -622,55 +621,54 @@ def main(wxDir, args): # TODO: wxBuilder = builder.MSVCProjectBuilder() - if not wxBuilder: print("Builder not available for your specified platform/compiler.") sys.exit(1) if options.clean: print("Performing cleanup.") - if ( - sys.platform.startswith("win") and - nmakeCommand.startswith('cmake') - ): - try: - shutil.rmtree(libdirrename) - except OSError: - pass - - try: - shutil.rmtree(libdirname) - except OSError: - pass - - files = [ - '.ninja_deps', - '.ninja_log', - 'cmake_install.cmake', - 'uninstall.cmake', - 'CMakeCache.txt', - 'CMakeFiles', - 'libs', - 'utils/CMakeFiles', - 'utils/cmake_install.cmake', - ] - - for f in files: - f = os.path.join(wxRootDir, f) - if not os.path.exists(f): - continue - if os.path.isdir(f): - try: - shutil.rmtree(f) - except OSError: - pass - elif os.path.isfile(f): - try: - os.remove(f) - except OSError: - pass - else: - wxBuilder.clean(dir=buildDir, options=args) + # if ( + # sys.platform.startswith("win") and + # nmakeCommand.startswith('cmake') + # ): + # try: + # shutil.rmtree(libdirrename) + # except OSError: + # pass + # + # try: + # shutil.rmtree(libdirname) + # except OSError: + # pass + # + # files = [ + # '.ninja_deps', + # '.ninja_log', + # 'cmake_install.cmake', + # 'uninstall.cmake', + # 'CMakeCache.txt', + # 'CMakeFiles', + # 'libs', + # 'utils/CMakeFiles', + # 'utils/cmake_install.cmake', + # ] + # + # for f in files: + # f = os.path.join(wxRootDir, f) + # if not os.path.exists(f): + # continue + # if os.path.isdir(f): + # try: + # shutil.rmtree(f) + # except OSError: + # pass + # elif os.path.isfile(f): + # try: + # os.remove(f) + # except OSError: + # pass + # else: + wxBuilder.clean(dir=buildDir, options=args) sys.exit(0) @@ -678,36 +676,37 @@ def main(wxDir, args): args.append(options.extra_make) if sys.platform.startswith("win"): - if nmakeCommand.startswith('cmake'): - args.insert(0, '-GNinja') + pass + # if nmakeCommand.startswith('cmake'): + # args.insert(0, '-GNinja') else: args.append("--jobs=" + options.jobs) exitIfError(wxBuilder.build(dir=buildDir, options=args), "Error building") - if sys.platform.startswith("win") and nmakeCommand.startswith('cmake'): - ninja_builder = builder.MSVCBuilder(commandName='ninja.exe') - for arg in args: - if arg.startswith('-B'): - break - else: - raise RuntimeError('Sanity check, this shouldn\'t happen') - ninja_args = [ - '-C"{0}"'.format(wxRootDir), - '-f"{0}"'.format(os.path.join(wxRootDir, 'build.ninja')), - '-j ' + options.jobs - ] - exitIfError(ninja_builder.build(dir=buildDir, options=ninja_args), "Error building") - print('renaming directory', libdirname, '->', libdirrename) - os.rename(libdirname, libdirrename) - for f in os.listdir(libdirrename): - if 'vc_custom' in f: - dst = f.replace('vc_custom', 'vc' + getVisCVersion()) - print('renaming file', f, '->', dst) - - dst = os.path.join(libdirrename, dst) - src = os.path.join(libdirrename, f) - os.rename(src, dst) + # if sys.platform.startswith("win") and nmakeCommand.startswith('cmake'): + # ninja_builder = builder.MSVCBuilder(commandName='ninja.exe') + # for arg in args: + # if arg.startswith('-B'): + # break + # else: + # raise RuntimeError('Sanity check, this shouldn\'t happen') + # ninja_args = [ + # '-C"{0}"'.format(wxRootDir), + # '-f"{0}"'.format(os.path.join(wxRootDir, 'build.ninja')), + # '-j ' + options.jobs + # ] + # exitIfError(ninja_builder.build(dir=buildDir, options=ninja_args), "Error building") + # print('renaming directory', libdirname, '->', libdirrename) + # os.rename(libdirname, libdirrename) + # for f in os.listdir(libdirrename): + # if 'vc_custom' in f: + # dst = f.replace('vc_custom', 'vc' + getVisCVersion()) + # print('renaming file', f, '->', dst) + # + # dst = os.path.join(libdirrename, dst) + # src = os.path.join(libdirrename, f) + # os.rename(src, dst) if options.install: extra=None diff --git a/buildtools/config.py b/buildtools/config.py index 71f915c9ee..be593149a1 100644 --- a/buildtools/config.py +++ b/buildtools/config.py @@ -906,6 +906,9 @@ def runcmd(cmd, getOutput=False, echoCmd=True, fatal=True, onError=None): output = output.rstrip() rval = sp.wait() + if output is not None: + output = output.replace('\x00', '') + if rval: # Failed! #raise subprocess.CalledProcessError(rval, cmd) From af5ecbe9d9ae76313a9f6a6faa4a46fc6ce61a68 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 1 Aug 2022 03:09:17 -0600 Subject: [PATCH 21/23] adds doing a complete build in setup.py --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 93ce1d23c5..7c7aeaf8a1 100644 --- a/setup.py +++ b/setup.py @@ -129,7 +129,11 @@ def run(self): 'message and the wxWidgets and Phoenix build steps in the future.\n') # Use the same Python that is running this script. - cmd = ['"{}"'.format(sys.executable), '-u', 'build.py', 'build'] + if isWindows: + cmd = ['"{}"'.format(sys.executable), '-u', 'build.py', 'dox', + 'etg', '--nodoc', 'sip', 'build_wx', 'build_py'] + else: + cmd = ['"{}"'.format(sys.executable), '-u', 'build.py', 'build'] cmd = ' '.join(cmd) runcmd(cmd) From a5e4390fa8823ead3a22a8997b591ebb9fff550e Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 1 Aug 2022 03:50:34 -0600 Subject: [PATCH 22/23] adds pyproject.toml --- pyproject.toml | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..edebeef8d6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[build-system] +requires=[ + "setuptools>=57.5.0", + "wheel", + "appdirs", + "sip==5.5.0", + "twine", + "requests", + "requests[security]", + "cython==0.29.24", + "pytest", + "pytest-xdist", + "pytest-forked", + "pytest-timeout", + "sphinx==2.2.0", + "doc2dash==2.3.0", + "beautifulsoup4" +] # PEP 508 specifications. +build-backend="setuptools.build_meta" + +[project] +name="wxPython" +authors=[ + {name="Robin Dunn", email="robin@alldunn.com"}, +] +description="Cross platform GUI toolkit for Python, \"Phoenix\" version" +long_description= "Welcome to wxPython's Project Phoenix! Phoenix is the improved next-generation wxPython, \"better, stronger, faster than he was before.\" This new implementation is focused on improving speed, maintainability and extensibility. Just like "Classic" wxPython, Phoenix wraps the wxWidgets C++ toolkit and provides access to the user interface portions of the wxWidgets API, enabling Python applications to have a native GUI on Windows, Macs or Unix systems, with a native look and feel and requiring very little (if any) platform specific code." +readme="README.md" +license={text="wxWindows Library License (https://opensource.org/licenses/wxwindows.php)"} +requires-python=">=3.6" +platforms=["WIN32", "WIN64", "OSX", "POSIX",] +keywords=["GUI", "wx", "wxWindows", "wxWidgets", "cross-platform", "user-interface", "awesome",] +zip-safe=False +classifiers=[ + "Development Status :: 6 - Mature", + "Environment :: MacOS X :: Cocoa", + "Environment :: Win32 (MS Windows)", + "Environment :: X11 Applications :: GTK", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows :: Windows 7", + "Operating System :: Microsoft :: Windows :: Windows 10", + "Operating System :: POSIX", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: User Interfaces", +] +dependencies=[ + "numpy", + "pillow", + "six", +] +dynamic=["version", ] + +[project.urls] +Homepage="http://wxPython.org/" +Download="https://pypi.org/project/wxPython" + +[project.scripts] +img2png="wx.tools.img2png:main" +img2py="wx.tools.img2py:main" +img2xpm="wx.tools.img2xpm:main" +pywxrc="wx.tools.pywxrc:main" +wxget="wx.tools.wxget:main" # New wx wget +wxdocs="wx.tools.wxget_docs_demo:docs_main" # Get/Launch Docs +wxdemo="wx.tools.wxget_docs_demo:demo_main" # Get/Launch Demo +helpviewer="wx.tools.helpviewer:main" +pycrust="wx.py.PyCrust:main" +pyshell="wx.py.PyShell:main" +pyslices="wx.py.PySlices:main" +pyslicesshell="wx.py.PySlicesShell:main" + + +[tool.setuptools.packages.find] +where=["wx"] From cfe21f95797c8b3da0008fd2bb04508e8dc61309 Mon Sep 17 00:00:00 2001 From: Kevin Schlosser Date: Mon, 1 Aug 2022 23:32:18 -0600 Subject: [PATCH 23/23] removes msvc from buildtools and grabs pyMSVC from pypi --- build.py | 4 +- buildtools/build_wxwidgets.py | 10 +- buildtools/config.py | 10 +- buildtools/msvc/__init__.py | 3719 --------------------------------- buildtools/msvc/vswhere.py | 2288 -------------------- pyproject.toml | 1 + requirements/devel.txt | 1 + 7 files changed, 16 insertions(+), 6017 deletions(-) delete mode 100644 buildtools/msvc/__init__.py delete mode 100644 buildtools/msvc/vswhere.py diff --git a/build.py b/build.py index 6decdae502..64cc9444d8 100755 --- a/build.py +++ b/build.py @@ -789,7 +789,7 @@ def checkCompiler(quiet=False): global win_environment if win_environment is None: - from buildtools import msvc + import pyMSVC # NOQA # setup the build environment making the minimum conpiler version # 14.2 A user does not need to have a full blown Visual Studio @@ -829,7 +829,7 @@ def checkCompiler(quiet=False): # using the /std compiler switch. You have to specifically tell # the compiler to update the value for the __cplusplus macro # using "/Zc:__cplusplus" - win_environment = msvc.setup_environment(minimum_c_version=14.2) + win_environment = pyMSVC.setup_environment(minimum_c_version=14.2) if not quiet: # this gives a simple output of the build environment. diff --git a/buildtools/build_wxwidgets.py b/buildtools/build_wxwidgets.py index b55ad89510..a90c48e2db 100644 --- a/buildtools/build_wxwidgets.py +++ b/buildtools/build_wxwidgets.py @@ -69,9 +69,9 @@ def getVisCVersion(): global win_environment if win_environment is None: - from . import msvc + import pyMSVC # NOQA - win_environment = msvc.setup_environment(minimum_c_version=14.2) + win_environment = pyMSVC.setup_environment(minimum_c_version=14.2) return win_environment.visual_c.version.split('.')[0] + '0' @@ -447,9 +447,9 @@ def main(wxDir, args): args = [] if toolkit == "msvc": - print("setting build options...") - from . import msvc - environment = msvc.setup_environment(minimum_c_version=14.2) + # print("setting build options...") + # import pyMSVC # NOQA + # environment = pyMSVC.setup_environment(minimum_c_version=14.2) # if ( # not options.jom and # environment.visual_c.has_cmake and diff --git a/buildtools/config.py b/buildtools/config.py index be593149a1..acfb147bf6 100644 --- a/buildtools/config.py +++ b/buildtools/config.py @@ -906,6 +906,10 @@ def runcmd(cmd, getOutput=False, echoCmd=True, fatal=True, onError=None): output = output.rstrip() rval = sp.wait() + + # on Windows for some reason the output can contain a bunch of NULL + # characters. this ends up adding a space at each character in the output. + # here we do a simple check for NULL characters and remove them. if output is not None: output = output.replace('\x00', '') @@ -959,10 +963,10 @@ def getSipFiles(names): return files - def getVisCVersion(): - from . import msvc - environment = msvc.setup_environment(minimum_c_version=14.2) + import pyMSVC # NOQA + + environment = pyMSVC.setup_environment(minimum_c_version=14.2) version = environment.visual_c.version return version.split('.')[0] + '0' diff --git a/buildtools/msvc/__init__.py b/buildtools/msvc/__init__.py deleted file mode 100644 index d4af6c0dc9..0000000000 --- a/buildtools/msvc/__init__.py +++ /dev/null @@ -1,3719 +0,0 @@ -# -*- coding: utf-8 -*- -# -# ############################################################################# -# -# MIT License -# -# Copyright 2022 Kevin G. Schlosser -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# -# ############################################################################# - -# This tool is used to create an identical build environment to what is created -# when building a Visual Studio project or using any of the vcvars and vsvars -# batch files. There is a similar tool included with SetupTools and it is -# called msvc. The setup tools version is not complete and it is also error -# prone. It does not make an identical build environment. - -import os -import sys -import ctypes -import subprocess -import winreg -import distutils.log -from typing import Optional, Union - - -_IS_WIN = sys.platform.startswith('win') - - -if _IS_WIN: - try: - from . import vswhere - except ImportError: - import vswhere - - -_HRESULT = ctypes.c_long -_BOOL = ctypes.c_bool -_DWORD = ctypes.c_ulong -_LPCVOID = ctypes.c_void_p -_LPCWSTR = ctypes.c_wchar_p -_LPVOID = ctypes.c_void_p -_UINT = ctypes.c_uint -_INT = ctypes.c_int -_HANDLE = ctypes.c_void_p -_HWND = ctypes.c_void_p -_LPWSTR = ctypes.c_wchar_p -_POINTER = ctypes.POINTER -_CHAR = _INT -_PUINT = _POINTER(_UINT) -_LPDWORD = _POINTER(_DWORD) - - -if _IS_WIN: - try: - _vswhere = vswhere.SetupConfiguration.GetSetupConfiguration() - except: # NOQA - _vswhere = None -else: - _vswhere = None - - -# noinspection PyPep8Naming -class _VS_FIXEDFILEINFO(ctypes.Structure): - _fields_ = [ - ("dwSignature", _DWORD), # will be 0xFEEF04BD - ("dwStrucVersion", _DWORD), - ("dwFileVersionMS", _DWORD), - ("dwFileVersionLS", _DWORD), - ("dwProductVersionMS", _DWORD), - ("dwProductVersionLS", _DWORD), - ("dwFileFlagsMask", _DWORD), - ("dwFileFlags", _DWORD), - ("dwFileOS", _DWORD), - ("dwFileType", _DWORD), - ("dwFileSubtype", _DWORD), - ("dwFileDateMS", _DWORD), - ("dwFileDateLS", _DWORD) - ] - - -if _IS_WIN: - _version = ctypes.windll.version - - _GetFileVersionInfoSize = _version.GetFileVersionInfoSizeW - _GetFileVersionInfoSize.restype = _DWORD - _GetFileVersionInfoSize.argtypes = [_LPCWSTR, _LPDWORD] - - _VerQueryValue = _version.VerQueryValueW - _VerQueryValue.restype = _BOOL - _VerQueryValue.argtypes = [_LPCVOID, _LPCWSTR, _POINTER(_LPVOID), _PUINT] - - _GetFileVersionInfo = _version.GetFileVersionInfoW - _GetFileVersionInfo.restype = _BOOL - _GetFileVersionInfo.argtypes = [_LPCWSTR, _DWORD, _DWORD, _LPVOID] - - - def _get_file_version(filename): - dw_len = _GetFileVersionInfoSize(filename, None) - if not dw_len: - raise ctypes.WinError() - - lp_data = (_CHAR * dw_len)() - if not _GetFileVersionInfo( - filename, - 0, - ctypes.sizeof(lp_data), lp_data - ): - raise ctypes.WinError() - - u_len = _UINT() - lpffi = _POINTER(_VS_FIXEDFILEINFO)() - lplp_buffer = ctypes.cast(ctypes.pointer(lpffi), _POINTER(_LPVOID)) - if not _VerQueryValue(lp_data, "\\", lplp_buffer, ctypes.byref(u_len)): - raise ctypes.WinError() - - ffi = lpffi.contents - return ( - ffi.dwFileVersionMS >> 16, - ffi.dwFileVersionMS & 0xFFFF, - ffi.dwFileVersionLS >> 16, - ffi.dwFileVersionLS & 0xFFFF, - ) - - - _CSIDL_PROGRAM_FILES = 0x26 - _CSIDL_PROGRAM_FILESX86 = 0x2A - - _SHGFP_TYPE_CURRENT = 0 - _MAX_PATH = 260 - _CSIDL_FLAG_DONT_VERIFY = 16384 - - _shell32 = ctypes.windll.Shell32 - - # noinspection PyUnboundLocalVariable - _SHGetFolderPathW = _shell32.SHGetFolderPathW - _SHGetFolderPathW.restype = _HRESULT - _SHGetFolderPathW.argtypes = [_HWND, _INT, _HANDLE, _DWORD, _LPWSTR] - - _buf = ctypes.create_unicode_buffer(_MAX_PATH) - - _SHGetFolderPathW( - 0, - _CSIDL_PROGRAM_FILESX86 | _CSIDL_FLAG_DONT_VERIFY, - 0, - _SHGFP_TYPE_CURRENT, - _buf - ) - - _PROGRAM_FILES_X86 = _buf.value - - _buf = ctypes.create_unicode_buffer(_MAX_PATH) - - _SHGetFolderPathW( - 0, - _CSIDL_PROGRAM_FILES | _CSIDL_FLAG_DONT_VERIFY, - 0, - _SHGFP_TYPE_CURRENT, - _buf - ) - - _PROGRAM_FILES = _buf.value - - del _buf - del _SHGetFolderPathW - del _shell32 - del _CSIDL_PROGRAM_FILES - del _CSIDL_PROGRAM_FILESX86 - del _SHGFP_TYPE_CURRENT - del _MAX_PATH - del _CSIDL_FLAG_DONT_VERIFY - -else: - def _get_file_version(_): - pass - - _PROGRAM_FILES_X86 = '' - _PROGRAM_FILES = '' - - -_found_cl = {} - - -def _find_cl(path): - if path in _found_cl: - return _found_cl[path] - - for root, dirs, files in os.walk(path): - if 'cl.exe' not in files: - continue - - if 'MSVC' in root: - head, tail = os.path.split(root) - - while head and not head.endswith('MSVC'): - head, tail = os.path.split(head) - - if head: - _found_cl[path] = [[head.split('\\VC\\')[0] + '\\VC', tail]] - else: - _found_cl[path] = [] - else: - root = root.split('\\VC\\')[0] - ver = os.path.split(root)[1] - - _found_cl[path] = [[root + '\\VC', ver.split(' ')[-1]]] - - return _found_cl[path] - - _found_cl[path] = [] - - return [] - - -def _get_program_files_vc(): - pths = [ - os.path.join(_PROGRAM_FILES_X86, f) - for f in os.listdir(_PROGRAM_FILES_X86) - if 'Visual Studio' in f - ] - res = [ - item for pth in pths for item in _find_cl(pth) - ] - - return res - - -def _get_reg_value(path, key, wow6432=False): - d = _read_reg_values(path, wow6432) - if key in d: - return d[key] - - return '' - - -def _read_reg_keys(key, wow6432=False): - if isinstance(key, tuple): - root = key[0] - key = key[1] - else: - root = winreg.HKEY_LOCAL_MACHINE - key = 'SOFTWARE\\Microsoft\\' + key - - try: - if wow6432: - handle = winreg.OpenKey( - root, - key, - 0, - winreg.KEY_READ | winreg.KEY_WOW64_32KEY - ) - else: - handle = winreg.OpenKeyEx(root, key) - except winreg.error: - return [] - res = [] - - for i in range(winreg.QueryInfoKey(handle)[0]): - res += [winreg.EnumKey(handle, i)] - - winreg.CloseKey(handle) - return res - - -def _read_reg_values(key, wow6432=False): - if isinstance(key, tuple): - root = key[0] - key = key[1] - else: - root = winreg.HKEY_LOCAL_MACHINE - key = 'SOFTWARE\\Microsoft\\' + key - - try: - if wow6432: - handle = winreg.OpenKey( - root, - key, - 0, - winreg.KEY_READ | winreg.KEY_WOW64_32KEY - ) - else: - handle = winreg.OpenKeyEx(root, key) - except winreg.error: - return {} - res = {} - for i in range(winreg.QueryInfoKey(handle)[1]): - name, value, _ = winreg.EnumValue(handle, i) - res[_convert_mbcs(name)] = _convert_mbcs(value) - - winreg.CloseKey(handle) - - return res - - -def _convert_mbcs(s): - dec = getattr(s, "decode", None) - if dec is not None: - try: - s = dec("mbcs") - except UnicodeError: - pass - return s - - -def _convert_version(ver): - if isinstance(ver, str): - ver = tuple(int(item) for item in ver.split('.')) - elif isinstance(ver, bytes): - ver = tuple( - int(item) for item in ver.decode('utf-8').split('.') - ) - elif isinstance(ver, int): - ver = (ver,) - elif isinstance(ver, float): - ver = tuple( - int(item) for item in str(ver).split('.') - ) - elif isinstance(ver, list): - ver = tuple(int(item) for item in ver) - - if not isinstance(ver, tuple): - raise TypeError( - 'Version is not correct type({0})'.format(type(ver)) - ) - - ver = '.'.join(str(item) for item in ver) - - return ver - - -# I have separated the environment into several classes -# Environment - the main environment class. -# the environment class is what is going to get used. this handles all of the -# non specific bits of the environment. all of the rest of the classes are -# brought together in the environment to form a complete build environment. - -# NETInfo - Any .NET related environment settings - -# WindowsSDKInfo - Any Windows SDK environment settings - -# VisualStudioInfo - Any VisualStudios environment settings (if applicable) - -# VisualCInfo - Any VisualC environment settings - -# PythonInfo - This class really isnt for environment settings as such. -# It is more of a convenience class. it will get things like a list of the -# includes specific to the python build. the architecture of the version of -# python that is running stuff along those lines. -class PythonInfo(object): - - @property - def architecture(self): - return 'x64' if sys.maxsize > 2 ** 32 else 'x86' - - @property - def version(self): - return '.'.join(str(ver) for ver in sys.version_info) - - @property - def dependency(self): - return 'Python%d%d.lib' % sys.version_info[:2] - - @property - def includes(self): - python_path = os.path.dirname(sys.executable) - python_include = os.path.join(python_path, 'include') - - python_includes = [python_include] - for root, dirs, files in os.walk(python_include): - for d in dirs: - python_includes += [os.path.join(root, d)] - return python_includes - - @property - def libraries(self): - python_path = os.path.dirname(sys.executable) - python_lib = os.path.join(python_path, 'libs') - - python_libs = [python_lib] - for root, dirs, files in os.walk(python_lib): - for d in dirs: - python_libs += [os.path.join(root, d)] - return python_libs - - def __str__(self): - template = ( - '== Python =====================================================\n' - ' version: {py_version}\n' - ' architecture: {py_architecture}\n' - ' library: {py_dependency}\n' - ' libs: {py_libraries}\n' - ' includes: {py_includes}\n' - ) - return template.format( - py_version=self.version, - py_architecture=self.architecture, - py_dependency=self.dependency, - py_libraries=self.libraries, - py_includes=self.includes - ) - - -class VisualCInfo(object): - - def __init__( - self, - environ: "Environment", - minimum_c_version: Optional[Union[int, float]] = None, - strict_c_version: Optional[Union[int, float]] = None, - minimum_toolkit_version: Optional[int] = None, - strict_toolkit_version: Optional[int] = None, - vs_version: Optional[Union[str, int]] = None - ): - self.environment = environ - self.platform = environ.platform - self.strict_c_version = strict_c_version - self.__installed_versions = None - self._cpp_installation = None - self._ide_install_directory = None - self._install_directory = None - self._cpp_version = None - self._tools_version = None - self._toolset_version = None - self._msvc_dll_path = None - self._tools_redist_directory = None - self._tools_install_directory = None - self._msbuild_version = None - self._msbuild_path = None - self._product_semantic_version = None - self._devinit_path = None - - self._strict_toolkit_version = strict_toolkit_version - self._minimum_toolkit_version = minimum_toolkit_version - py_version = sys.version_info[:2] - if py_version in ((3, 4),): - min_visual_c_version = 10.0 - elif py_version in ((3, 5), (3, 6), (3, 7), (3, 8)): - min_visual_c_version = 14.0 - elif py_version in ((3, 9), (3, 10)): - min_visual_c_version = 14.2 - else: - raise RuntimeError( - 'This library does not support ' - 'python version %d.%d' % py_version - ) - - if ( - strict_c_version is not None and - strict_c_version < min_visual_c_version - ): - raise RuntimeError( - 'The set minimum compiler version is lower then the ' - 'required compiler version for Python' - ) - - if minimum_c_version is None: - minimum_c_version = min_visual_c_version - - if strict_toolkit_version is not None: - strict_toolkit_version = str(strict_toolkit_version / 10.0) - - if minimum_toolkit_version is not None: - minimum_toolkit_version = str(minimum_toolkit_version / 10.0) - - self.minimum_c_version = minimum_c_version - self._strict_toolkit_version = strict_toolkit_version - self._minimum_toolkit_version = minimum_toolkit_version - - if _vswhere is not None: - distutils.log.debug('\n' + str(_vswhere)) - - cpp_id = 'Microsoft.VisualCpp.Tools.Host{0}.Target{1}'.format( - environ.machine_architecture.upper(), - environ.python.architecture.upper() - ) - - tools_id = ( - 'Microsoft.VisualCpp.Premium.Tools.' - 'Host{0}.Target{1}' - ).format( - environ.machine_architecture.upper(), - environ.python.architecture.upper() - ) - - cpp_version = None - cpp_installation = None - - for installation in _vswhere: - if vs_version is not None: - try: - if isinstance(vs_version, str): - display_version = str( - installation.catalog.product_display_version - ) - - if ( - installation.version != vs_version and - display_version != vs_version - ): - continue - else: - product_line_version = int( - installation.catalog.product_line_version - ) - - if product_line_version != vs_version: - continue - - except: # NOQA - continue - - for package in installation.packages.vsix: - if package.id == cpp_id: - if ( - self.strict_c_version is not None and - package != self.strict_c_version - ): - continue - - if ( - self.minimum_c_version is not None and - package < self.minimum_c_version - ): - continue - - if cpp_version is None: - cpp_version = package - cpp_installation = installation - elif package > cpp_version: - cpp_version = package - cpp_installation = installation - - if cpp_installation is not None: - self._cpp_version = cpp_version.version - self._cpp_installation = cpp_installation - - tools_version = None - tools_path = os.path.join( - cpp_installation.path, - 'VC', - 'Tools', - 'MSVC' - ) - - for package in cpp_installation.packages.vsix: - if package.id == tools_id: - if not os.path.exists( - os.path.join(tools_path, package.version) - ): - continue - - if strict_toolkit_version is not None: - if package == strict_toolkit_version: - tools_version = package - break - - if minimum_toolkit_version is not None: - if package <= minimum_toolkit_version: - continue - - if tools_version is None: - tools_version = package - elif tools_version > package: - tools_version = package - - if tools_version is None: - for file in os.listdir(tools_path): - if strict_toolkit_version is not None: - tk_version = file[:len(strict_toolkit_version)] - if tk_version == strict_toolkit_version: - tools_version = file - break - - if minimum_toolkit_version is not None: - tk_version = file[:len(minimum_toolkit_version)] - if tk_version < minimum_toolkit_version: - continue - - if tools_version is None: - tools_version = file - elif tools_version < file: - tools_version = file - - else: - tools_version = tools_version.version - - if tools_version is not None: - tools_version = tools_version.split('.') - self._toolset_version = ( - 'v' + tools_version[0] + - tools_version[1][0] - ) - self._tools_version = '.'.join(tools_version) - - self._tools_install_directory = os.path.join( - tools_path, self._tools_version - ) - - tools_redist_directory = ( - self._tools_install_directory.replace( - 'Tools', - 'Redist' - ) - ) - - if os.path.exists(tools_redist_directory): - self._tools_redist_directory = ( - tools_redist_directory + '\\' - ) - - msvc_dll_path = os.path.join( - tools_redist_directory, - environ.python.architecture, - 'Microsoft.VC{0}.CRT'.format( - self._toolset_version[1:] - ) - ) - - if os.path.exists(msvc_dll_path): - self._msvc_dll_path = msvc_dll_path - - install_directory = os.path.join( - cpp_installation.path, 'VC' - ) - if os.path.exists(install_directory): - self._install_directory = install_directory - - msbuild_path = os.path.join( - cpp_installation.path, - 'MSBuild', - 'Current', - 'Bin', - 'MSBuild.exe' - ) - - if os.path.exists(msbuild_path): - msbuild_version = _get_file_version(msbuild_path) - self._msbuild_version = '.'.join( - str(item) for item in msbuild_version - ) - self._msbuild_path = msbuild_path - - ide_directory = os.path.join( - cpp_installation.path, - 'Common7', - 'IDE', - 'VC' - ) - if os.path.exists(ide_directory): - self._ide_install_directory = ide_directory - - product_semantic_version = ( - cpp_installation.catalog.product_semantic_version - ) - if product_semantic_version is not None: - self._product_semantic_version = ( - product_semantic_version.split('+')[0] - ) - - devinit_path = os.path.join( - cpp_installation.path, - 'Common7', - 'Tools', - 'devinit', - 'devinit.exe' - ) - if os.path.exists(devinit_path): - self._devinit_path = devinit_path - - @property - def has_ninja(self): - ide_path = os.path.split(self.ide_install_directory)[0] - ninja_path = os.path.join( - ide_path, - 'CommonExtensions', - 'Microsoft', - 'CMake', - 'Ninja', - 'ninja.exe' - ) - return os.path.exists(ninja_path) - - @property - def has_cmake(self): - ide_path = os.path.split(self.ide_install_directory)[0] - - cmake_path = os.path.join( - ide_path, - 'CommonExtensions', - 'Microsoft', - 'CMake', - 'CMake', - 'bin', - 'cmake.exe' - ) - return os.path.exists(cmake_path) - - @property - def cmake_paths(self) -> list: - paths = [] - ide_path = os.path.split(self.ide_install_directory)[0] - - cmake_path = os.path.join( - ide_path, - 'CommonExtensions', - 'Microsoft', - 'CMake' - ) - - if os.path.exists(cmake_path): - bin_path = os.path.join(cmake_path, 'CMake', 'bin') - ninja_path = os.path.join(cmake_path, 'Ninja') - if os.path.exists(bin_path): - paths += [bin_path] - - if os.path.exists(ninja_path): - paths += [ninja_path] - - return paths - - @property - def cpp_installation( - self - ) -> Union[vswhere.ISetupInstance, vswhere.ISetupInstance2]: - return self._cpp_installation - - @property - def f_sharp_path(self) -> Optional[str]: - version = float(int(self.version.split('.')[0])) - - reg_path = ( - winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\Microsoft\VisualStudio' - r'\{0:.1f}\Setup\F#'.format(version) - ) - - f_sharp_path = _get_reg_value(reg_path, 'ProductDir') - if f_sharp_path and os.path.exists(f_sharp_path): - return f_sharp_path - - path = r'C:\Program Files (x86)\Microsoft SDKs\F#' - if os.path.exists(path): - versions = os.listdir(path) - max_ver = 0.0 - found_version = '' - - for version in versions: - try: - ver = float(version) - except ValueError: - continue - - if ver > max_ver: - max_ver = ver - found_version = version - - f_sharp_path = os.path.join( - path, - found_version, - 'Framework', - 'v' + found_version - ) - - if os.path.exists(f_sharp_path): - return f_sharp_path - - install_dir = os.path.split(self.install_directory)[0] - f_sharp_path = os.path.join( - install_dir, - 'Common7', - 'IDE', - 'CommonExtensions', - 'Microsoft', - 'FSharp', - 'Tools' - ) - if os.path.exists(f_sharp_path): - return f_sharp_path - - @property - def ide_install_directory(self) -> str: - if self._ide_install_directory is None: - directory = self.install_directory - ide_directory = os.path.abspath( - os.path.join(directory, '..') - ) - - ide_directory = os.path.join( - ide_directory, - 'Common7', - 'IDE', - 'VC' - ) - if os.path.exists(ide_directory): - self._ide_install_directory = ide_directory - - return self._ide_install_directory - - @property - def install_directory(self) -> str: - """ - Visual C path - :return: Visual C path - """ - if self._install_directory is None: - self._install_directory = ( - self._installed_c_paths[self.version]['base'] - ) - - return self._install_directory - - @property - def _installed_c_paths(self): - if self.__installed_versions is None: - self.__installed_versions = {} - - def add(vers): - for base_pth, ver in vers: - if os.path.exists(base_pth): - base_ver = float(int(ver.split('.')[0])) - - self.__installed_versions[ver] = dict( - base=base_pth, - root=base_pth - ) - self.__installed_versions[base_ver] = dict( - base=base_pth, - root=base_pth - ) - - add(_get_program_files_vc()) - - reg_path = ( - winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\Policies\Microsoft' - r'\VisualStudio\Setup' - ) - - vs_path = _get_reg_value(reg_path, 'SharedInstallationPath') - - if vs_path: - vs_path = os.path.split(vs_path)[0] - if os.path.exists(vs_path): - res = [] - - pths = [ - os.path.join(vs_path, vs_ver) - for vs_ver in os.listdir(vs_path) - if vs_ver.isdigit() - ] - res.extend( - [item for pth in pths for item in _find_cl(pth)] - ) - - add(res) - - reg_path = ( - winreg.HKEY_CLASSES_ROOT, - r'Local Settings\Software' - r'\Microsoft\Windows\Shell\MuiCache' - ) - - paths = [] - - for key in _read_reg_values(reg_path): - if 'cl.exe' in key: - value = _get_reg_value(reg_path, key) - if 'C++ Compiler Driver' in value: - paths += [key] - - elif 'devenv.exe' in key: - if not os.path.exists(key): - continue - - value = _get_reg_value(reg_path, key) - - if value.startswith('Microsoft Visual Studio '): - - head, tail = os.path.split(key) - while tail != 'Common7' and head: - head, tail = os.path.split(head) - - if head: - add(_find_cl(head)) - - for path in paths: - if not os.path.exists(path): - continue - - if '\\VC\\bin' in path: - version = path.split('\\VC\\bin')[0] - elif '\\bin\\Host' in path: - version = path.split('\\bin\\Host')[0] - else: - continue - - version = os.path.split(version)[1] - version = version.replace( - 'Microsoft Visual Studio', - '' - ).strip() - base_version = float(int(version.split('.')[0])) - - base_path = path.split('\\VC\\')[0] + '\\VC' - if os.path.exists(os.path.join(base_path, 'include')): - vc_root = base_path - else: - vc_root = path.split('\\bin\\')[0] - - self.__installed_versions[version] = dict( - base=base_path, - root=vc_root - ) - self.__installed_versions[base_version] = dict( - base=base_path, - root=vc_root - ) - - reg_path = ( - winreg.HKEY_LOCAL_MACHINE, - 'SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7' - ) - - for key in _read_reg_values(reg_path): - try: - version = float(key) - except ValueError: - continue - - path = _get_reg_value(reg_path, key) - - if ( - ( - os.path.exists(path) and - version not in self.__installed_versions - ) or version == 15.0 - ): - - if version == 15.0: - version = 14.0 - - if not os.path.split(path)[1] == 'VC': - path = os.path.join(path, 'VC') - - self.__installed_versions[version] = dict( - base=path, - root=path - ) - self.__installed_versions[key] = dict( - base=path, - root=path - ) - - return self.__installed_versions - - @property - def version(self) -> str: - """ - Visual C version - - Sometimes when building extension in python the version of the compiler - that was used to compile Python has to also be used to compile an - extension. I have this system set so it will automatically pick the - most recent compiler installation. this can be overridden in 2 ways. - The first way being that the compiler version that built Python has to - be used. The second way is you can set a minimum compiler version to - use. - - :return: found Visual C version - """ - if self._cpp_version is None: - if self.strict_c_version is not None: - if self.strict_c_version not in self._installed_c_paths: - raise RuntimeError( - 'No Compatible Visual C version found.' - ) - - self._cpp_version = str(self.strict_c_version) - return self._cpp_version - - match = None - for version in self._installed_c_paths: - if not isinstance(version, float): - continue - - if version >= self.minimum_c_version: - if match is None: - match = version - elif version < match: - match = version - - if match is None: - raise RuntimeError( - 'No Compatible Visual C\\C++ version found.' - ) - - self._cpp_version = str(match) - - return self._cpp_version - - @property - def tools_version(self) -> str: - if self._tools_version is None: - version = os.path.split(self.tools_install_directory)[1] - if not version.split('.')[-1].isdigit(): - version = str(self.version) - - self._tools_version = version - - return self._tools_version - - @property - def toolset_version(self) -> str: - """ - The platform toolset gets written to the solution file. this instructs - the compiler to use the matching MSVCPxxx.dll file. - """ - - if self._toolset_version is None: - tools_version = self.tools_version.split('.') - - self._toolset_version = ( - 'v' + - tools_version[0] + - tools_version[1][:1] - ) - - return self._toolset_version - - @property - def msvc_dll_version(self) -> Optional[str]: - msvc_dll_path = self.msvc_dll_path - if not msvc_dll_path: - return - - for f in os.listdir(msvc_dll_path): - if f.endswith('dll'): - version = _get_file_version( - os.path.join(msvc_dll_path, f) - ) - return '.'.join(str(ver) for ver in version) - - @property - def msvc_dll_path(self) -> Optional[str]: - if self._msvc_dll_path is None: - x64 = self.platform == 'x64' - - toolset_version = self.toolset_version - - if toolset_version is None: - return None - - folder_names = ( - 'Microsoft.VC{0}.CRT'.format(toolset_version[1:]), - ) - - redist_path = self.tools_redist_directory - - for root, dirs, files in os.walk(redist_path): - def pass_directory(): - for item in ('onecore', 'arm', 'spectre'): - if item in root.lower(): - return True - return False - - if pass_directory(): - continue - - for folder_name in folder_names: - if folder_name in dirs: - if x64 and ('amd64' in root or 'x64' in root): - self._msvc_dll_path = os.path.join( - root, - folder_name - ) - break - elif ( - not x64 and - 'amd64' not in root - and 'x64' not in root - ): - self._msvc_dll_path = os.path.join( - root, - folder_name - ) - break - - return self._msvc_dll_path - - @property - def tools_redist_directory(self) -> Optional[str]: - if self._tools_redist_directory is None: - tools_install_path = self.tools_install_directory - - if 'MSVC' in tools_install_path: - redist_path = tools_install_path.replace( - 'Tools', - 'Redist' - ) - if ( - not os.path.exists(redist_path) and - 'BuildTools' in tools_install_path - ): - redist_path = redist_path.replace( - 'BuildRedist', 'BuildTools' - ) - else: - redist_path = os.path.join( - tools_install_path, - 'Redist' - ) - - if not os.path.exists(redist_path): - redist_path = os.path.split(redist_path)[0] - tools_version = None - - for version in os.listdir(redist_path): - if not os.path.isdir( - os.path.join(redist_path, version) - ): - continue - - if version.startswith('v'): - continue - - if self._strict_toolkit_version is not None: - tk_version = ( - version[:len(self._strict_toolkit_version)] - ) - if tk_version == self._strict_toolkit_version: - tools_version = version - break - - if self._minimum_toolkit_version is not None: - tk_version = ( - version[:len(self._minimum_toolkit_version)] - ) - if tk_version < self._minimum_toolkit_version: - continue - - if tools_version is None: - tools_version = version - elif tools_version < version: - tools_version = version - - if tools_version is not None: - self._tools_redist_directory = ( - os.path.join(redist_path, tools_version) - ) - else: - self._tools_redist_directory = '' - - else: - self._tools_redist_directory = redist_path - - if ( - self._tools_redist_directory and - not self._tools_redist_directory.endswith('\\') - ): - self._tools_redist_directory += '\\' - - return self._tools_redist_directory - - @property - def tools_install_directory(self) -> Optional[str]: - """ - Visual C compiler tools path. - :return: Path to the compiler tools - """ - if self._tools_install_directory is None: - - vc_version = float(int(self.version.split('.')[0])) - if vc_version >= 14.0: - vc_tools_path = self._installed_c_paths[vc_version]['root'] - else: - vc_tools_path = self._installed_c_paths[vc_version]['base'] - - # lib_path = os.path.join(vc_tools_path, 'lib') - tools_path = os.path.join(vc_tools_path, 'Tools', 'MSVC') - - if os.path.exists(tools_path): - versions = os.listdir(tools_path) - tools_version = None - - for version in versions: - if self._strict_toolkit_version is not None: - tk_version = ( - version[:len(self._strict_toolkit_version)] - ) - if tk_version == self._strict_toolkit_version: - tools_version = version - break - - if self._minimum_toolkit_version is not None: - tk_version = ( - version[:len(self._minimum_toolkit_version)] - ) - if tk_version < self._minimum_toolkit_version: - continue - - if tools_version is None: - tools_version = version - elif tools_version < version: - tools_version = version - - if tools_version is not None: - self._tools_install_directory = os.path.join( - tools_path, - tools_version - ) - - else: - raise RuntimeError('Unable to locate build tools') - - else: - raise RuntimeError('Unable to locate build tools') - - return self._tools_install_directory - - @property - def msbuild_version(self) -> Optional[str]: - """ - MSBuild versions are specific to the Visual C version - :return: MSBuild version, 3.5, 4.0, 12, 14, 15 - """ - if self._msbuild_version is None: - vc_version = str(float(int(self.version.split('.')[0]))) - - if vc_version == 9.0: - self._msbuild_version = '3.5' - elif vc_version in (10.0, 11.0): - self._msbuild_version = '4.0' - else: - self._msbuild_version = vc_version - return self._msbuild_version - - @property - def msbuild_path(self) -> Optional[str]: - if self._msbuild_path is not None: - program_files = os.environ.get( - 'ProgramFiles(x86)', - 'C:\\Program Files (x86)' - ) - - version = float(int(self.version.split('.')[0])) - - ms_build_path = os.path.join( - program_files, - 'MSBuild', - '{0:.1f}'.format(version), - 'bin' - ) - - if self.platform == 'x64': - if os.path.exists(os.path.join(ms_build_path, 'x64')): - ms_build_path = os.path.join(ms_build_path, 'x64') - else: - ms_build_path = os.path.join(ms_build_path, 'amd64') - - elif os.path.exists(os.path.join(ms_build_path, 'x86')): - ms_build_path = os.path.join(ms_build_path, 'x86') - - if os.path.exists(ms_build_path): - self._msbuild_path = ms_build_path - - return self._msbuild_path - - @property - def html_help_path(self) -> Optional[str]: - - reg_path = ( - winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\Wow6432Node\Microsoft\Windows' - r'\CurrentVersion\App Paths\hhw.exe' - ) - - html_help_path = _get_reg_value(reg_path, 'Path') - if html_help_path and os.path.exists(html_help_path): - return html_help_path - - if os.path.exists( - r'C:\Program Files (x86)\HTML Help Workshop' - ): - return r'C:\Program Files (x86)\HTML Help Workshop' - - @property - def path(self) -> list: - tools_path = self.tools_install_directory - base_path = os.path.join(tools_path, 'bin') - - path = [] - - f_sharp_path = self.f_sharp_path - msbuild_path = self.msbuild_path - - ide_base_path = os.path.split(self.install_directory)[0] - perf_tools_x64_path = os.path.join( - ide_base_path, - 'Team Tools', - 'Performance Tools', - 'x64' - ) - - if os.path.exists(perf_tools_x64_path): - path += [perf_tools_x64_path] - - perf_tools_path = os.path.join( - ide_base_path, - 'Team Tools', - 'Performance Tools' - ) - if os.path.exists(perf_tools_path): - path += [perf_tools_path] - - com7_ide_path = os.path.join( - ide_base_path, - 'Common7', - 'IDE' - ) - - vc_packages_path = os.path.join( - com7_ide_path, - 'VC', - 'VCPackages' - ) - if os.path.exists(vc_packages_path): - path += [vc_packages_path] - - team_explorer_path = os.path.join( - com7_ide_path, - 'CommonExtensions', - 'Microsoft', - 'TeamFoundation', - 'Team Explorer' - ) - if os.path.exists(team_explorer_path): - path += [team_explorer_path] - - intellicode_cli_path = os.path.join( - com7_ide_path, - 'Extensions', - 'Microsoft', - 'IntelliCode', - 'CLI' - ) - if os.path.exists(intellicode_cli_path): - path += [intellicode_cli_path] - - roslyn_path = os.path.join( - ide_base_path, - 'MSBuild', - 'Current', - 'bin', - 'Roslyn' - ) - if os.path.exists(roslyn_path): - path += [roslyn_path] - - devinit_path = os.path.join( - ide_base_path, - 'Common7', - 'Tools', - 'devinit' - ) - if os.path.exists(devinit_path): - path += [devinit_path] - - vs_path = os.path.split(ide_base_path)[0] - vs_path, edition = os.path.split(vs_path) - - collection_tools_path = os.path.join( - vs_path, - 'Shared', - 'Common', - 'VSPerfCollectionTools', - 'vs' + edition - ) - if os.path.exists(collection_tools_path): - path += [collection_tools_path] - - collection_tools_path_x64 = os.path.join( - collection_tools_path, - 'x64' - ) - if os.path.exists(collection_tools_path_x64): - path += [collection_tools_path_x64] - - if msbuild_path is not None: - path += [os.path.split(msbuild_path)[0]] - - if f_sharp_path is not None: - path += [f_sharp_path] - - html_help_path = self.html_help_path - if html_help_path is not None: - path += [html_help_path] - - bin_path = os.path.join( - base_path, - 'Host' + self.platform, - self.platform - ) - - if not os.path.exists(bin_path): - if self.platform == 'x64': - bin_path = os.path.join(base_path, 'x64') - if not os.path.exists(bin_path): - bin_path = os.path.join(base_path, 'amd64') - else: - bin_path = os.path.join(base_path, 'x86') - if not os.path.exists(bin_path): - bin_path = base_path - - if os.path.exists(bin_path): - path += [bin_path] - - path += self.cmake_paths - - return path - - @property - def atlmfc_lib_path(self) -> Optional[str]: - atlmfc_path = self.atlmfc_path - if not atlmfc_path: - return - - atlmfc = os.path.join(atlmfc_path, 'lib') - if self.platform == 'x64': - atlmfc_path = os.path.join(atlmfc, 'x64') - if not os.path.exists(atlmfc_path): - atlmfc_path = os.path.join(atlmfc, 'amd64') - else: - atlmfc_path = os.path.join(atlmfc, 'x86') - if not os.path.exists(atlmfc_path): - atlmfc_path = atlmfc - - if os.path.exists(atlmfc_path): - return atlmfc_path - - @property - def lib(self) -> list: - tools_path = self.tools_install_directory - path = os.path.join(tools_path, 'lib') - - if self.platform == 'x64': - lib_path = os.path.join(path, 'x64') - if not os.path.exists(lib_path): - lib_path = os.path.join(path, 'amd64') - - else: - lib_path = os.path.join(path, 'x86') - - if not os.path.exists(lib_path): - lib_path = path - - lib = [] - if os.path.exists(lib_path): - lib += [lib_path] - - atlmfc_path = self.atlmfc_lib_path - if atlmfc_path is not None: - lib += [atlmfc_path] - - return lib - - @property - def lib_path(self) -> list: - tools_path = self.tools_install_directory - path = os.path.join(tools_path, 'lib') - - if self.platform == 'x64': - lib = os.path.join(path, 'x64') - if not os.path.exists(lib): - lib = os.path.join(path, 'amd64') - else: - lib = os.path.join(path, 'x86') - if not os.path.exists(lib): - lib = path - - references_path = os.path.join( - lib, - 'store', - 'references' - ) - - lib_path = [] - if os.path.exists(lib): - lib_path += [lib] - - atlmfc_path = self.atlmfc_lib_path - - if atlmfc_path is not None: - lib_path += [atlmfc_path] - - if os.path.exists(references_path): - lib_path += [references_path] - else: - references_path = os.path.join(path, 'x86', 'store', 'references') - if os.path.exists(references_path): - lib_path += [references_path] - - return lib_path - - @property - def atlmfc_path(self) -> Optional[str]: - tools_path = self.tools_install_directory - atlmfc_path = os.path.join(tools_path, 'ATLMFC') - - if os.path.exists(atlmfc_path): - return atlmfc_path - - @property - def atlmfc_include_path(self) -> Optional[str]: - atlmfc_path = self.atlmfc_path - if atlmfc_path is None: - return - - atlmfc_include_path = os.path.join( - atlmfc_path, - 'include' - ) - if os.path.exists(atlmfc_include_path): - return atlmfc_include_path - - @property - def include(self) -> list: - tools_path = self.tools_install_directory - include_path = os.path.join(tools_path, 'include') - atlmfc_path = self.atlmfc_include_path - - include = [] - if os.path.exists(include_path): - include += [include_path] - - if atlmfc_path is not None: - include += [atlmfc_path] - return include - - def __iter__(self): - ide_install_directory = self.ide_install_directory - tools_install_directory = self.tools_install_directory - install_directory = self.install_directory - - if ide_install_directory: - ide_install_directory += '\\' - - if tools_install_directory: - tools_install_directory += '\\' - - if install_directory: - install_directory += '\\' - - env = dict( - VCIDEInstallDir=ide_install_directory, - VCToolsVersion=self.tools_version, - VCToolsInstallDir=tools_install_directory, - VCINSTALLDIR=install_directory, - VCToolsRedistDir=self.tools_redist_directory, - Path=self.path, - LIB=self.lib, - INCLUDE=self.include, - LIBPATH=self.lib_path, - FSHARPINSTALLDIR=self.f_sharp_path - ) - - html_help = self.html_help_path - - if html_help is not None: - env['HTMLHelpDir'] = html_help - - if self._product_semantic_version is not None: - env['VSCMD_VER'] = self._product_semantic_version - - if self._devinit_path is not None: - env['__devinit_path'] = self._devinit_path - - for key, value in env.items(): - if value is not None and value: - if isinstance(value, list): - value = os.pathsep.join(value) - yield key, str(value) - - def __str__(self): - template = ( - '== Visual C ===================================================\n' - ' version: {visual_c_version}\n' - ' path: {visual_c_path}\n' - ' has cmake: {has_cmake}\n' - ' has ninja: {has_ninja}\n' - '\n' - ' -- Tools ---------------------------------------------------\n' - ' version: {tools_version}\n' - ' path: {tools_install_path}\n' - ' redist path: {vc_tools_redist_path}\n' - ' -- F# ------------------------------------------------------\n' - ' path: {f_sharp_path}\n' - ' -- DLL -----------------------------------------------------\n' - ' version: {platform_toolset}-{msvc_dll_version}\n' - ' path: {msvc_dll_path}\n' - '\n' - '== MSBuild ====================================================\n' - ' version: {msbuild_version}\n' - ' path: {msbuild_path}\n' - '\n' - '== HTML Help ==================================================\n' - ' path: {html_help_path}\n' - '\n' - '== ATLMFC =====================================================\n' - ' path: {atlmfc_path}\n' - ' include path: {atlmfc_include_path}\n' - ' lib path: {atlmfc_lib_path}\n' - ) - - return template.format( - visual_c_version=self.version, - visual_c_path=self.install_directory, - has_cmake=self.has_cmake, - has_ninja=self.has_ninja, - tools_version=self.tools_version, - tools_install_path=self.tools_install_directory, - vc_tools_redist_path=self.tools_redist_directory, - platform_toolset=self.toolset_version, - msvc_dll_version=self.msvc_dll_version, - msvc_dll_path=self.msvc_dll_path, - msbuild_version=self.msbuild_version, - msbuild_path=self.msbuild_path, - f_sharp_path=self.f_sharp_path, - html_help_path=self.html_help_path, - atlmfc_lib_path=self.atlmfc_lib_path, - atlmfc_include_path=self.atlmfc_include_path, - atlmfc_path=self.atlmfc_path, - ) - - -class VisualStudioInfo(object): - - def __init__( - self, - environ: "Environment", - c_info: VisualCInfo - ): - self.environment = environ - self.__devenv_version = None - self.c_info = c_info - self._install_directory = None - self._dev_env_directory = None - self._common_tools = None - - installation = c_info.cpp_installation - - self.__name__ = 'VisualStudioInfo' - - if installation is not None: - if installation.path.endswith('BuildTools'): - self.__name__ = 'BuildToolsInfo' - version = installation.version.split('.')[0] - self.__devenv_version = ( - str(float(int(version))), - installation.version - ) - - install_directory = installation.path - if os.path.exists(install_directory): - self._install_directory = install_directory - - dev_env_directory = os.path.join( - install_directory, - os.path.split(installation.product_path)[0] - ) - if os.path.exists(dev_env_directory): - self._dev_env_directory = dev_env_directory - - common_tools = os.path.join( - install_directory, 'Common7', 'Tools' - ) - if os.path.exists(common_tools): - self._common_tools = common_tools + '\\' - - @property - def install_directory(self) -> str: - if self._install_directory is None: - install_dir = os.path.join( - self.c_info.install_directory, - '..' - ) - - self._install_directory = ( - os.path.abspath(install_dir) - ) - return self._install_directory - - @property - def dev_env_directory(self) -> str: - if self._dev_env_directory is None: - self._dev_env_directory = os.path.join( - self.install_directory, - 'Common7', - 'IDE' - ) - - return self._dev_env_directory - - @property - def common_tools(self) -> str: - if self._common_tools is None: - - common_tools = os.path.join( - self.install_directory, - 'Common7', - 'Tools' - ) - if os.path.exists(common_tools): - self._common_tools = common_tools + '\\' - else: - self._common_tools = '' - - return self._common_tools - - @property - def path(self) -> list: - path = [] - - dev_env_directory = self.dev_env_directory - if dev_env_directory: - path.append(dev_env_directory) - - common_tools = self.common_tools - - if common_tools: - path.append(common_tools[:-1]) - - collection_tools_dir = _get_reg_value( - 'VisualStudio\\VSPerf', - 'CollectionToolsDir' - ) - if ( - collection_tools_dir and - os.path.exists(collection_tools_dir) - ): - path.append(collection_tools_dir) - - vs_ide_path = self.dev_env_directory - - test_window_path = os.path.join( - vs_ide_path, - 'CommonExtensions', - 'Microsoft', - 'TestWindow' - ) - - vs_tdb_path = os.path.join( - vs_ide_path, - 'VSTSDB', - 'Deploy' - ) - - if os.path.exists(vs_tdb_path): - path.append(vs_tdb_path) - - if os.path.exists(test_window_path): - path.append(test_window_path) - - return path - - @property - def __version(self): - if not isinstance(self.__devenv_version, tuple): - dev_env_dir = self.dev_env_directory - - if dev_env_dir is not None: - command = ''.join([ - '"', - os.path.join(dev_env_dir, 'devenv'), - '" /?\n' - ]) - - proc = subprocess.Popen( - 'cmd', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE, - shell=True - ) - - proc.stdin.write(command.encode('utf-8')) - out, err = proc.communicate() - proc.stdin.close() - - err = err.strip() - - if err: - self.__devenv_version = (None, None) - return self.__devenv_version - - for line in out.decode('utf-8').split('\n'): - if ' Visual Studio ' in line: - break - else: - self.__devenv_version = (None, None) - return self.__devenv_version - - line = line.rstrip('.').strip() - line = line.split(' Visual Studio ')[-1] - - common_version, version = line.split(' Version ') - - self.__devenv_version = (common_version, version) - else: - self.__devenv_version = (None, None) - - return self.__devenv_version - - @property - def common_version(self) -> Optional[str]: - return self.__version[0] - - @property - def uncommon_version(self) -> Optional[str]: - return self.__version[1] - - @property - def version(self) -> Optional[float]: - version = self.uncommon_version - - if version is not None: - return float(int(version.split('.')[0])) - - def __iter__(self): - install_directory = self.install_directory - dev_env_directory = self.dev_env_directory - - if install_directory: - install_directory += '\\' - - if dev_env_directory: - dev_env_directory += '\\' - - env = dict( - Path=self.path, - VSINSTALLDIR=install_directory, - DevEnvDir=dev_env_directory, - VisualStudioVersion=self.version - ) - - toolsets = { - 'v142': '160', - 'v141': '150', - 'v140': '140', - 'v120': '120', - 'v110': '110', - 'v100': '100', - 'v90': '90' - } - - toolset_version = self.c_info.toolset_version - - if toolset_version in toolsets: - comn_tools = 'VS{0}COMNTOOLS'.format( - toolsets[toolset_version] - ) - env[comn_tools] = self.common_tools - - for key, value in env.items(): - if value is not None and value: - if isinstance(value, list): - value = os.pathsep.join(value) - yield key, str(value) - - def __str__(self): - installation = self.c_info.cpp_installation - - if installation is None: - return '' - - template = ( - '== {name} \n' - ' description: {description}\n' - ' install date: {install_date}\n' - ' version: {version}\n' - ' version (friendly): {product_line_version}\n' - ' display version: {product_display_version}\n' - ' path: {path}\n' - ' executable: {product_path}\n' - ' is complete: {is_complete}\n' - ' is prerelease: {is_prerelease}\n' - ' is launchable: {is_launchable}\n' - ' state: {state}\n' - ) - - name = installation.display_name - - if name is None: - if self.__name__ == 'VisualStudioInfo': - name = 'Visual Studio' - else: - name = 'Build Tools' - - description = installation.description - path = installation.path - install_date = installation.install_date.strftime('%c') - version = installation.version - is_complete = installation.is_complete - is_prerelease = installation.is_prerelease - is_launchable = installation.is_launchable - state = ', '.join(installation.state) - product_path = os.path.join(path, installation.product_path) - - catalog = installation.catalog - product_display_version = catalog.product_display_version - product_line_version = catalog.product_line_version - - res = template.format( - name=name, - description=description, - install_date=install_date, - path=path, - version=version, - is_complete=is_complete, - is_prerelease=is_prerelease, - is_launchable=is_launchable, - product_path=product_path, - product_display_version=product_display_version, - product_line_version=product_line_version, - state=state - ) - - res = res.split('\n', 1) - res[0] += '=' * (63 - len(res[0])) - return '\n'.join(res) - - -class WindowsSDKInfo(object): - - def __init__( - self, - environ: "Environment", - c_info: VisualCInfo, - minimum_sdk_version: Optional[str] = None, - strict_sdk_version: Optional[str] = None - ): - self.environment = environ - self.c_info = c_info - self.platform = environ.platform - self.vc_version = c_info.version - - if ( - strict_sdk_version is not None and - strict_sdk_version.startswith('10.0') - ): - if strict_sdk_version.count('.') == 2: - strict_sdk_version += '.0' - - if ( - minimum_sdk_version is not None and - minimum_sdk_version.startswith('10.0') - ): - if minimum_sdk_version.count('.') == 2: - minimum_sdk_version += '.0' - - self._minimum_sdk_version = minimum_sdk_version - self._strict_sdk_version = strict_sdk_version - - self._directory = None - self._sdk_version = None - self._version = None - - @property - def extension_sdk_directory(self) -> Optional[str]: - version = self.version - - if version.startswith('10'): - extension_path = os.path.join( - _PROGRAM_FILES_X86, - 'Microsoft SDKs', - 'Windows Kits', - '10', - 'ExtensionSDKs' - ) - if os.path.exists(extension_path): - return extension_path - - sdk_path = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'InstallationFolder' - ) - - if sdk_path: - sdk_path = sdk_path.replace( - 'Windows Kits', - 'Microsoft SDKs\\Windows Kits' - ) - extension_path = os.path.join( - sdk_path[:-1], - 'Extension SDKs' - ) - - if os.path.exists(extension_path): - return extension_path - - @property - def lib_version(self) -> str: - return self.sdk_version - - @property - def ver_bin_path(self) -> str: - bin_path = self.bin_path[:-1] - - version = self.version - - if version == '10.0': - version = self.sdk_version - - ver_bin_path = os.path.join(bin_path, version) - if os.path.exists(ver_bin_path): - return ver_bin_path - else: - return bin_path - - @property - def mssdk(self) -> Optional[str]: - return self.directory - - @property - def ucrt_version(self) -> Optional[str]: - if self.version == '10.0': - sdk_version = self.sdk_version - else: - sdk_versions = _read_reg_keys( - 'Microsoft SDKs\\Windows', - True - ) - if 'v10.0' in sdk_versions: - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'ProductVersion', - True - ) - else: - return - - if sdk_version.endswith('0'): - return sdk_version - else: - return sdk_version[:-1] - - @property - def ucrt_lib_directory(self) -> Optional[str]: - if self.version == '10.0': - directory = self.directory - directory = os.path.join( - directory, - 'Lib', - self.sdk_version, - 'ucrt', - self.platform - ) - - else: - sdk_versions = _read_reg_keys( - 'Microsoft SDKs\\Windows', - True - ) - if 'v10.0' in sdk_versions: - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'ProductVersion', - True - ) - directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'InstallationFolder', - True - ) - directory = os.path.join( - directory, - 'Lib', - sdk_version, - 'ucrt', - self.platform - ) - else: - return - - if os.path.exists(directory): - if not directory.endswith('\\'): - return directory + '\\' - - return directory - - @property - def ucrt_headers_directory(self) -> Optional[str]: - if self.version == '10.0': - directory = self.directory - directory = os.path.join( - directory, - 'Include', - self.sdk_version, - 'ucrt' - ) - - else: - sdk_versions = _read_reg_keys( - 'Microsoft SDKs\\Windows', - True - ) - if 'v10.0' in sdk_versions: - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'ProductVersion', - True - ) - directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'InstallationFolder', - True - ) - directory = os.path.join( - directory, - 'Include', - sdk_version, - 'ucrt' - ) - else: - return - - if os.path.exists(directory): - if not directory.endswith('\\'): - return directory + '\\' - - return directory - - @property - def ucrt_sdk_directory(self) -> Optional[str]: - directory = self.directory - - if self.version != '10.0': - sdk_versions = _read_reg_keys( - 'Microsoft SDKs\\Windows', - True - ) - if 'v10.0' in sdk_versions: - directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v10.0', - 'InstallationFolder', - True - ) - else: - return - - if os.path.exists(directory): - if not directory.endswith('\\'): - return directory + '\\' - - return directory - - @property - def bin_path(self) -> Optional[str]: - directory = self.directory - if directory: - bin_path = os.path.join( - self.directory, - 'bin' - ) - - return bin_path + '\\' - - @property - def lib(self) -> list: - directory = self.directory - if not directory: - return [] - - version = self.version - if version == '10.0': - version = self.sdk_version - - lib = [] - - base_lib = os.path.join( - directory, - 'lib', - version, - ) - if not os.path.exists(base_lib): - base_lib = os.path.join( - directory, - 'lib' - ) - - if os.path.exists(base_lib): - if self.platform == 'x64': - if os.path.exists(os.path.join(base_lib, 'x64')): - lib += [os.path.join(base_lib, 'x64')] - - ucrt = os.path.join(base_lib, 'ucrt', self.platform) - um = os.path.join(base_lib, 'um', self.platform) - if not os.path.exists(ucrt): - ucrt = os.path.join(base_lib, 'ucrt', 'amd64') - - if not os.path.exists(um): - um = os.path.join(base_lib, 'um', 'amd64') - - else: - lib += [base_lib] - ucrt = os.path.join(base_lib, 'ucrt', self.platform) - um = os.path.join(base_lib, 'um', self.platform) - - if not os.path.exists(ucrt): - ucrt = os.path.join(base_lib, 'ucrt') - - if not os.path.exists(um): - um = os.path.join(base_lib, 'um') - - if os.path.exists(ucrt): - lib += [ucrt] - else: - ucrt = self.ucrt_lib_directory - if ucrt is not None: - lib += [ucrt] - - if os.path.exists(um): - lib += [um] - - return lib - - @property - def path(self) -> list: - path = [] - ver_bin_path = self.ver_bin_path - - if self.platform == 'x64': - bin_path = os.path.join(ver_bin_path, 'x64') - if not os.path.exists(bin_path): - bin_path = os.path.join( - ver_bin_path, - 'amd64' - ) - else: - bin_path = os.path.join(ver_bin_path, 'x86') - - if not os.path.exists(bin_path): - bin_path = ver_bin_path - - if os.path.exists(bin_path): - path += [bin_path] - - bin_path = self.bin_path - bin_path = os.path.join( - bin_path, - 'x64' - ) - - if os.path.exists(bin_path): - path += [bin_path] - - type_script_path = self.type_script_path - if type_script_path is not None: - path += [type_script_path] - - return path - - @property - def type_script_path(self) -> Optional[str]: - program_files = os.environ.get( - 'ProgramFiles(x86)', - 'C:\\Program Files (x86)' - ) - type_script_path = os.path.join( - program_files, - 'Microsoft SDKs', - 'TypeScript' - ) - - if os.path.exists(type_script_path): - max_ver = 0.0 - for version in os.listdir(type_script_path): - try: - version = float(version) - except ValueError: - continue - max_ver = max(max_ver, version) - - type_script_path = os.path.join( - type_script_path, - str(max_ver) - ) - - if os.path.exists(type_script_path): - return type_script_path - - @property - def include(self) -> list: - directory = self.directory - if self.version == '10.0': - include_path = os.path.join( - directory, - 'include', - self.sdk_version - ) - else: - include_path = os.path.join( - directory, - 'include' - ) - - includes = [include_path] - - for path in ('ucrt', 'cppwinrt', 'shared', 'um', 'winrt'): - pth = os.path.join(include_path, path) - if os.path.exists(pth): - includes += [pth] - elif path == 'ucrt' and self.version != '10.0': - ucrt = self.ucrt_headers_directory - if ucrt is not None: - includes += [ucrt] - - gl_include = os.path.join(include_path, 'gl') - - if os.path.exists(gl_include): - includes += [gl_include] - - return includes - - @property - def lib_path(self) -> list: - return self.sdk_lib_path - - @property - def sdk_lib_path(self) -> list: - directory = self.directory - version = self.version - - if version == '10.0': - version = self.sdk_version - - union_meta_data = os.path.join( - directory, - 'UnionMetadata', - version - ) - references = os.path.join( - directory, - 'References', - version - ) - - lib_path = [] - - if os.path.exists(union_meta_data): - lib_path += [union_meta_data] - - if os.path.exists(references): - lib_path += [references] - - return lib_path - - @property - def windows_sdks(self) -> list: - """ - Windows SDK versions that are compatible with Visual C - :return: compatible Windows SDK versions - """ - ver = int(self.vc_version.split('.')[0]) - - sdk_versions = [] - if ver >= 14: - sdk_versions.extend(['v10.0']) - if ver >= 12: - sdk_versions.extend(['v8.1a', 'v8.1']) - if ver >= 11: - sdk_versions.extend(['v8.0a', 'v8.0']) - if ver >= 10: - sdk_versions.extend(['v7.1a', 'v7.1', 'v7.0a']) - - sdk_versions.extend(['v7.0', 'v6.1', 'v6.0a']) - - if ( - self._minimum_sdk_version is not None and - self._minimum_sdk_version in sdk_versions - ): - index = sdk_versions.index(self._minimum_sdk_version) - sdk_versions = sdk_versions[:index + 1] - - return sdk_versions - - @property - def version(self) -> str: - """ - This is used in the solution file to tell the compiler what SDK to use. - We obtain a list of compatible Windows SDK versions for the - Visual C version. We check and see if any of the compatible SDK's are - installed and if so we return that version. - - :return: Installed Windows SDK version - """ - - if self._version is None: - sdk_versions = _read_reg_keys('Microsoft SDKs\\Windows', True) - if self._strict_sdk_version is not None: - if self._strict_sdk_version.startswith('10.0'): - keys = _read_reg_keys('Windows Kits\\Installed Roots') - if self._strict_sdk_version in keys: - self._version = '10.0' - return self._version - - raise RuntimeError( - 'Unable to locate Windows SDK ' + - self._strict_sdk_version - ) - - if 'v' + self._strict_sdk_version in sdk_versions: - self._version = self._strict_sdk_version - return self._version - - for sdk in self.windows_sdks: - if sdk in sdk_versions: - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\' + sdk, - 'ProductVersion', - True - ) - - if sdk_version.startswith('10.0'): - while sdk_version.count('.') < 3: - sdk_version += '.0' - - if self._strict_sdk_version is not None: - if self._strict_sdk_version == sdk_version: - self._version = sdk[1:] - return self._version - - continue - - if self._minimum_sdk_version is not None: - if self._minimum_sdk_version.count('.') > 1: - if sdk_version < self._minimum_sdk_version: - break - - self._version = sdk[1:] - return self._version - - if self._strict_sdk_version is not None: - raise RuntimeError( - 'Unable to locate Windows SDK version ' + - self._strict_sdk_version - ) - - if self._minimum_sdk_version is not None: - raise RuntimeError( - 'Unable to locate Windows SDK vesion >= ' + - self._minimum_sdk_version - ) - - raise RuntimeError('Unable to locate Windows SDK') - - return self._version - - @property - def sdk_version(self) -> str: - """ - This is almost identical to target_platform. Except it returns the - actual version of the Windows SDK not the truncated version. - - :return: actual Windows SDK version - """ - - if self._sdk_version is None: - version = self.version - - if self._strict_sdk_version is None: - self._sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'ProductVersion', - True - ) - if self._sdk_version.startswith('10.0'): - while self._sdk_version.count('.') < 3: - self._sdk_version += '.0' - - return self._sdk_version - - if self._strict_sdk_version == version: - self._sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'ProductVersion', - True - ) - - if self._sdk_version.startswith('10.0'): - while self._sdk_version.count('.') < 3: - self._sdk_version += '.0' - - return self._sdk_version - - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'ProductVersion', - True - ) - - if sdk_version.startswith('10.0'): - while sdk_version.count('.') < 3: - sdk_version += '.0' - - if self._strict_sdk_version == sdk_version: - self._sdk_version = sdk_version - return self._sdk_version - - if self._strict_sdk_version.startswith('10.0'): - keys = _read_reg_keys('Windows Kits\\Installed Roots') - if self._strict_sdk_version in keys: - self._sdk_version = self._strict_sdk_version - return self._sdk_version - - return self._sdk_version - - @property - def directory(self) -> Optional[str]: - """ - Path to the Windows SDK version that has been found. - :return: Windows SDK path - """ - - if self._directory is None: - version = self.version - - if self._strict_sdk_version is None: - self._directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'InstallationFolder', - True - ) - return self._directory - - if self._strict_sdk_version == version: - self._directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'InstallationFolder', - True - ) - return self._directory - - sdk_version = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'ProductVersion' - ) - - if sdk_version.startswith('10.0'): - while sdk_version.count('.') < 3: - sdk_version += '.0' - - if self._strict_sdk_version == sdk_version: - self._directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'InstallationFolder', - True - ) - return self._directory - - if self._strict_sdk_version.startswith('10.0'): - keys = _read_reg_keys('Windows Kits\\Installed Roots') - if self._strict_sdk_version in keys: - self._directory = _get_reg_value( - 'Microsoft SDKs\\Windows\\v' + version, - 'InstallationFolder', - True - ) - - return self._directory - - return self._directory - - def __iter__(self): - ver_bin_path = self.ver_bin_path - directory = self.directory - - if ver_bin_path: - ver_bin_path += '\\' - - if directory and not directory.endswith('\\'): - directory += '\\' - - lib_version = self.lib_version - if not lib_version.endswith('\\'): - lib_version += '\\' - - sdk_version = self.sdk_version - if not sdk_version.endswith('\\'): - sdk_version += '\\' - - env = dict( - LIB=self.lib, - Path=self.path, - LIBPATH=self.lib_path, - INCLUDE=self.include, - UniversalCRTSdkDir=self.ucrt_sdk_directory, - ExtensionSdkDir=self.extension_sdk_directory, - WindowsSdkVerBinPath=ver_bin_path, - UCRTVersion=self.ucrt_version, - WindowsSDKLibVersion=lib_version, - WindowsSDKVersion=sdk_version, - WindowsSdkDir=directory, - WindowsLibPath=self.lib_path, - WindowsSdkBinPath=self.bin_path, - DISTUTILS_USE_SDK=1, - MSSDK=self.directory - ) - - for key, value in env.items(): - if value is not None and value: - if isinstance(value, list): - value = os.pathsep.join(value) - yield key, str(value) - - def __str__(self): - template = ( - '== Windows SDK ================================================\n' - ' version: {target_platform}\n' - ' sdk version: {windows_sdk_version}\n' - ' path: {target_platform_path}\n' - '\n' - '== Universal CRT ==============================================\n' - ' version: {ucrt_version}\n' - ' path: {ucrt_sdk_directory}\n' - ' lib directory: {ucrt_lib_directory}\n' - ' headers directory: {ucrt_headers_directory}\n' - '\n' - '== Extension SDK ==============================================\n' - ' path: {extension_sdk_directory}\n' - '\n' - '== TypeScript =================================================\n' - ' path: {type_script_path}\n' - ) - - return template.format( - target_platform=self.version, - windows_sdk_version=self.sdk_version, - target_platform_path=self.directory, - extension_sdk_directory=self.extension_sdk_directory, - ucrt_sdk_directory=self.ucrt_sdk_directory, - ucrt_headers_directory=self.ucrt_headers_directory, - ucrt_lib_directory=self.ucrt_lib_directory, - ucrt_version=self.ucrt_version, - type_script_path=self.type_script_path, - ) - - -class NETInfo(object): - - def __init__( - self, - environ: "Environment", - c_info: VisualCInfo, - sdk_version: str, - minimum_net_version: Optional[str] = None, - strict_net_version: Optional[str] = None - ): - - self.environment = environ - self.platform = environ.platform - self.c_info = c_info - self.vc_version = c_info.version - self.sdk_version = sdk_version - self._minimum_net_version = minimum_net_version - self._strict_net_version = strict_net_version - self._version_32 = None - self._version_64 = None - - @property - def version(self) -> str: - """ - .NET Version - :return: returns the version associated with the architecture - """ - if self.platform == 'x64': - return self.version_64 - else: - return self.version_32 - - @property - def version_32(self) -> str: - """ - .NET 32bit framework version - :return: x86 .NET framework version - """ - if self._version_32 is None: - target_framework = None - installation = self.c_info.cpp_installation - - if installation is not None: - for package in installation.packages.msi: - if ( - package.id.startswith('Microsoft.Net') and - package.id.endswith('TargetingPack') - ): - - if self._strict_net_version is not None: - if self._strict_net_version == package: - self._version_32 = 'v' + package.version - return self._version_32 - else: - continue - - if self._minimum_net_version is not None: - if package < self._minimum_net_version: - continue - - if target_framework is None: - target_framework = package - elif package > target_framework: - target_framework = package - - if target_framework is not None: - self._version_32 = 'v' + target_framework.version - return self._version_32 - - target_framework = _get_reg_value( - 'VisualStudio\\SxS\\VC7', - 'FrameworkVer32' - ) - - if target_framework: - if self._strict_net_version is not None: - if target_framework[1:] == self._strict_net_version: - self._version_32 = target_framework - return self._version_32 - elif self._minimum_net_version is not None: - if target_framework[1:] >= self._strict_net_version: - self._version_32 = target_framework - return self._version_32 - else: - self._version_32 = target_framework - return self._version_32 - - versions = list( - key for key in _read_reg_keys('.NETFramework\\', True) - if key.startswith('v') - ) - - target_framework = None - - if self._strict_net_version is not None: - if 'v' + self._strict_net_version in versions: - self._version_32 = 'v' + self._strict_net_version - return self._version_32 - else: - for version in versions: - if self._minimum_net_version is not None: - if version[1:] < self._minimum_net_version: - continue - - if target_framework is None: - target_framework = version[1:] - elif target_framework < version[1:]: - target_framework = version[1:] - - if target_framework is not None: - self._version_32 = 'v' + target_framework - return self._version_32 - - net_path = os.path.join( - '%SystemRoot%', - 'Microsoft.NET', - 'Framework' - ) - net_path = os.path.expandvars(net_path) - if os.path.exists(net_path): - versions = [item[1:] for item in os.listdir(net_path)] - if self._strict_net_version is not None: - if self._strict_net_version in versions: - self._version_32 = 'v' + self._strict_net_version - return self._version_32 - - raise RuntimeError( - 'Unable to locate .NET version ' + - self._strict_net_version - ) - - for version in versions: - if ( - self._minimum_net_version is not None and - version < self._minimum_net_version - ): - continue - - if target_framework is None: - target_framework = version - elif target_framework < version: - target_framework = version - - if target_framework: - self._version_32 = 'v' + target_framework - return self._version_32 - - if self._strict_net_version is not None: - raise RuntimeError( - 'Unable to locate .NET version ' + - self._strict_net_version - ) - - if self._minimum_net_version is not None: - raise RuntimeError( - 'Unable to locate .NET version ' + - self._minimum_net_version + - ' or above.' - ) - - self._version_32 = '' - - return self._version_32 - - @property - def version_64(self) -> str: - """ - .NET 64bit framework version - :return: x64 .NET framework version - """ - - if self._version_64 is None: - target_framework = None - installation = self.c_info.cpp_installation - - if installation is not None: - for package in installation.packages.msi: - if ( - package.id.startswith('Microsoft.Net') and - package.id.endswith('TargetingPack') - ): - - if self._strict_net_version is not None: - if self._strict_net_version == package: - self._version_64 = 'v' + package.version - return self._version_64 - else: - continue - - if self._minimum_net_version is not None: - if package < self._minimum_net_version: - continue - - if target_framework is None: - target_framework = package - elif package > target_framework: - target_framework = package - - if target_framework is not None: - self._version_64 = 'v' + target_framework.version - return self._version_64 - - target_framework = _get_reg_value( - 'VisualStudio\\SxS\\VC7', - 'FrameworkVer64' - ) - - if target_framework: - if self._strict_net_version is not None: - if target_framework[1:] == self._strict_net_version: - self._version_64 = target_framework - return self._version_64 - - elif self._minimum_net_version is not None: - if target_framework[1:] >= self._strict_net_version: - self._version_64 = target_framework - return self._version_64 - - else: - self._version_64 = target_framework - return self._version_64 - - versions = list( - key for key in _read_reg_keys('.NETFramework\\', True) - if key.startswith('v') - ) - - target_framework = None - - if self._strict_net_version is not None: - if 'v' + self._strict_net_version in versions: - self._version_64 = 'v' + self._strict_net_version - return self._version_64 - else: - for version in versions: - if self._minimum_net_version is not None: - if version[1:] < self._minimum_net_version: - continue - - if target_framework is None: - target_framework = version[1:] - elif target_framework < version[1:]: - target_framework = version[1:] - - if target_framework is not None: - self._version_64 = 'v' + target_framework - return self._version_64 - - net_path = os.path.join( - '%SystemRoot%', - 'Microsoft.NET', - 'Framework64' - ) - net_path = os.path.expandvars(net_path) - if os.path.exists(net_path): - versions = [item[1:] for item in os.listdir(net_path)] - if self._strict_net_version is not None: - if self._strict_net_version in versions: - self._version_64 = 'v' + self._strict_net_version - return self._version_64 - - raise RuntimeError( - 'Unable to locate .NET version ' + - self._strict_net_version - ) - - for version in versions: - if ( - self._minimum_net_version is not None and - version < self._minimum_net_version - ): - continue - - if target_framework is None: - target_framework = version - elif target_framework < version: - target_framework = version - - if target_framework: - self._version_64 = 'v' + target_framework - return self._version_64 - - if self._strict_net_version is not None: - raise RuntimeError( - 'Unable to locate .NET version ' + - self._strict_net_version - ) - - if self._minimum_net_version is not None: - raise RuntimeError( - 'Unable to locate .NET version ' + - self._minimum_net_version + - ' or above.' - ) - - self._version_64 = '' - - return self._version_64 - - @property - def directory(self) -> str: - if self.platform == 'x64': - return self.directory_64 - else: - return self.directory_32 - - @property - def directory_32(self) -> str: - """ - .NET 32bit path - :return: path to x86 .NET - """ - directory = _get_reg_value( - 'VisualStudio\\SxS\\VC7\\', - 'FrameworkDir32' - ) - if not directory: - directory = os.path.join( - '%SystemRoot%', - 'Microsoft.NET', - 'Framework' - ) - directory = os.path.expandvars(directory) - else: - directory = directory[:-1] - - if os.path.exists(directory): - return directory - - return '' - - @property - def directory_64(self) -> str: - """ - .NET 64bit path - :return: path to x64 .NET - """ - - directory = _get_reg_value( - 'VisualStudio\\SxS\\VC7\\', - 'FrameworkDir64' - ) - if not directory: - directory = os.path.join( - '%SystemRoot%', - 'Microsoft.NET', - 'Framework64' - ) - directory = os.path.expandvars(directory) - else: - directory = directory[:-1] - - if os.path.exists(directory): - return directory - - return '' - - @property - def preferred_bitness(self) -> str: - return '32' if self.platform == 'x86' else '64' - - @property - def netfx_sdk_directory(self) -> Optional[str]: - framework = '.'.join(self.version[1:].split('.')[:2]) - ver = float(int(self.vc_version.split('.')[0])) - - if ver in (9.0, 10.0, 11.0, 12.0): - key = 'Microsoft SDKs\\Windows\\v{0}\\'.format(self.sdk_version) - else: - key = 'Microsoft SDKs\\NETFXSDK\\{0}\\'.format(framework) - - net_fx_path = _get_reg_value( - key, - 'KitsInstallationFolder', - wow6432=True - ) - - if net_fx_path and os.path.exists(net_fx_path): - return net_fx_path - - @property - def net_fx_tools_directory(self) -> Optional[str]: - framework = self.version[1:].split('.')[:2] - - if framework[0] == '4': - net_framework = '40' - else: - net_framework = ''.join(framework) - - net_fx_key = ( - 'WinSDK-NetFx{framework}Tools-{platform}' - ).format( - framework=''.join(net_framework), - platform=self.platform - ) - - framework = '.'.join(framework) - ver = float(int(self.vc_version.split('.')[0])) - - if ver in (9.0, 10.0, 11.0, 12.0): - key = 'Microsoft SDKs\\Windows\\v{0}\\{1}\\'.format( - self.sdk_version, - net_fx_key - ) - - if self.sdk_version in ('6.0A', '6.1'): - key = key.replace(net_fx_key, 'WinSDKNetFxTools') - - net_fx_path = _get_reg_value( - key, - 'InstallationFolder', - wow6432=True - ) - else: - key = 'Microsoft SDKs\\NETFXSDK\\{0}\\{1}\\'.format( - framework, - net_fx_key - ) - net_fx_path = _get_reg_value( - key, - 'InstallationFolder', - wow6432=True - ) - - if net_fx_path and os.path.exists(net_fx_path): - return net_fx_path - - @property - def add(self) -> str: - return '__DOTNET_ADD_{0}BIT'.format(self.preferred_bitness) - - @property - def net_tools(self) -> list: - - version = float(int(self.vc_version.split('.')[0])) - if version <= 10.0: - include32 = True - include64 = self.platform == 'x64' - else: - include32 = self.platform == 'x86' - include64 = self.platform == 'x64' - - tools = [] - if include32: - tools += [ - os.path.join(self.directory_32, self.version_32) - ] - if include64: - tools += [ - os.path.join(self.directory_64, self.version_64) - ] - - return tools - - @property - def executable_path_x64(self) -> Optional[str]: - tools_directory = self.net_fx_tools_directory - if not tools_directory: - return - - if 'NETFX' in tools_directory: - if 'x64' in tools_directory: - return tools_directory - else: - tools_directory = os.path.join(tools_directory, 'x64') - if os.path.exists(tools_directory): - return tools_directory - - @property - def executable_path_x86(self) -> Optional[str]: - tools_directory = self.net_fx_tools_directory - if not tools_directory: - return - - if 'NETFX' in tools_directory: - if 'x64' in tools_directory: - return ( - os.path.split(os.path.split(tools_directory)[0])[0] + '\\' - ) - else: - return tools_directory - return None - - @property - def lib(self) -> list: - sdk_directory = self.netfx_sdk_directory - if not sdk_directory: - return [] - - sdk_directory = os.path.join(sdk_directory, 'lib', 'um') - - if self.platform == 'x64': - lib_dir = os.path.join(sdk_directory, 'x64') - if not os.path.exists(lib_dir): - lib_dir = os.path.join(sdk_directory, 'amd64') - else: - lib_dir = os.path.join(sdk_directory, 'x86') - if not os.path.exists(lib_dir): - lib_dir = sdk_directory - - if os.path.exists(lib_dir): - return [lib_dir] - - return [] - - @property - def path(self) -> list: - path = [] - directory = self.directory - - pth = os.path.join( - directory, - self.version - ) - - if os.path.exists(pth): - path += [pth] - else: - match = None - version = self.version - versions = [ - item[1:] for item in os.listdir(directory) - if item.startswith('v') - ] - for ver in versions: - if version > ver: - if match is None: - match = ver - elif ver > match: - match = ver - - if match is not None: - pth = os.path.join( - directory, - 'v' + match - ) - path += [pth] - - net_fx_tools = self.net_fx_tools_directory - if net_fx_tools: - path += [net_fx_tools] - - return path - - @property - def lib_path(self) -> list: - path = [] - directory = self.directory - - pth = os.path.join( - directory, - self.version - ) - - if os.path.exists(pth): - path += [pth] - else: - match = None - version = self.version - versions = [ - item[1:] for item in os.listdir(directory) - if item.startswith('v') - ] - for ver in versions: - if version > ver: - if match is None: - match = ver - elif ver > match: - match = ver - - if match is not None: - pth = os.path.join( - directory, - 'v' + match - ) - path += [pth] - - return path - - @property - def include(self) -> list: - sdk_directory = self.netfx_sdk_directory - - if sdk_directory: - include_dir = os.path.join(sdk_directory, 'include', 'um') - - if os.path.exists(include_dir): - return [include_dir] - - return [] - - def __iter__(self): - directory = self.directory - if directory: - directory += '\\' - - env = dict( - WindowsSDK_ExecutablePath_x64=self.executable_path_x64, - WindowsSDK_ExecutablePath_x86=self.executable_path_x86, - LIB=self.lib, - Path=self.path, - LIBPATH=self.lib_path, - INCLUDE=self.include, - __DOTNET_PREFERRED_BITNESS=self.preferred_bitness, - FrameworkDir=directory, - NETFXSDKDir=self.netfx_sdk_directory, - ) - - version = self.version[1:].split('.') - if version[0] == '4': - version = ['4', '0'] - else: - version = version[:2] - - net_p = 'v' + ('.'.join(version)) - - env[self.add] = '1' - if self.platform == 'x64': - directory_64 = self.directory_64 - - if directory_64: - loc = os.path.join(directory_64, net_p) - - if os.path.exists(loc): - version_64 = loc - else: - for p in os.listdir(directory_64): - if not os.path.isdir(os.path.join(directory_64, p)): - continue - - if p[1:] < net_p[1:]: - continue - - version_64 = p - break - else: - version_64 = self.version_64 - - directory_64 += '\\' - else: - version_64 = None - - env['FrameworkDir64'] = directory_64 - env['FrameworkVersion64'] = version_64 - env['FrameworkVersion'] = version_64 - - else: - directory_32 = self.directory_32 - - if directory_32: - loc = os.path.join(directory_32, net_p) - - if not os.path.exists(loc): - for p in os.listdir(directory_32): - if not os.path.isdir(os.path.join(directory_32, p)): - continue - - if p[1:] < net_p[1:]: - continue - - version_32 = p - break - else: - version_32 = self.version_64 - - else: - version_32 = loc - - directory_32 += '\\' - else: - version_32 = None - - env['FrameworkDir32'] = directory_32 - env['FrameworkVersion32'] = version_32 - env['FrameworkVersion'] = version_32 - - framework = env['FrameworkVersion'][1:].split('.')[:2] - - framework_version_key = ( - 'Framework{framework}Version'.format(framework=''.join(framework)) - ) - env[framework_version_key] = 'v' + '.'.join(framework) - - for key, value in env.items(): - if value is not None and value: - if isinstance(value, list): - value = os.pathsep.join(value) - yield key, str(value) - - def __str__(self): - template = ( - '== .NET =======================================================\n' - ' version: {target_framework}\n' - '\n' - ' -- x86 -----------------------------------------------------\n' - ' version: {framework_version_32}\n' - ' path: {framework_dir_32}\n' - ' -- x64 -----------------------------------------------------\n' - ' version: {framework_version_64}\n' - ' path: {framework_dir_64}\n' - ' -- NETFX ---------------------------------------------------\n' - ' path: {net_fx_tools_directory}\n' - ' x86 exe path: {executable_path_x86}\n' - ' x64 exe path: {executable_path_x64}\n' - ) - return template.format( - target_framework=self.version, - framework_version_32=self.version_32, - framework_dir_32=self.directory_32, - framework_version_64=self.version_64, - framework_dir_64=self.directory_64, - net_fx_tools_directory=self.net_fx_tools_directory, - executable_path_x64=self.executable_path_x64, - executable_path_x86=self.executable_path_x86, - ) - - -class Environment(object): - _original_environment = {k_: v_ for k_, v_ in os.environ.items()} - - def __init__( - self, - minimum_c_version: Optional[Union[int, float]] = None, - strict_c_version: Optional[Union[int, float]] = None, - minimum_toolkit_version: Optional[int] = None, - strict_toolkit_version: Optional[int] = None, - minimum_sdk_version: Optional[str] = None, - strict_sdk_version: Optional[str] = None, - minimum_net_version: Optional[str] = None, - strict_net_version: Optional[str] = None, - vs_version: Optional[Union[str, int]] = None - ): - self.python = PythonInfo() - - self.visual_c = VisualCInfo( - self, - minimum_c_version, - strict_c_version, - minimum_toolkit_version, - strict_toolkit_version, - vs_version - ) - - self.visual_studio = VisualStudioInfo( - self, - self.visual_c - ) - - self.windows_sdk = WindowsSDKInfo( - self, - self.visual_c, - minimum_sdk_version, - strict_sdk_version - ) - - self.dot_net = NETInfo( - self, - self.visual_c, - self.windows_sdk.version, - minimum_net_version, - strict_net_version - ) - - def reset_environment(self): - for key in list(os.environ.keys())[:]: - if key not in self._original_environment: - del os.environ[key] - - os.environ.update(self._original_environment) - - @property - def machine_architecture(self): - import platform - return 'x64' if '64' in platform.machine() else 'x86' - - @property - def platform(self): - """ - :return: x86 or x64 - """ - import platform - - win_64 = self.machine_architecture == 'x64' - python_64 = platform.architecture()[0] == '64bit' and win_64 - - return 'x64' if python_64 else 'x86' - - @property - def configuration(self): - """ - Build configuration - :return: one of ReleaseDLL, DebugDLL - """ - - if os.path.splitext(sys.executable)[0].endswith('_d'): - config = 'Debug' - else: - config = 'Release' - - return config - - def __iter__(self): - for item in self.build_environment.items(): - yield item - - @property - def build_environment(self): - """ - This would be the work horse. This is where all of the gathered - information is put into a single container and returned. - The information is then added to os.environ in order to allow the - build process to run properly. - - List of environment variables generated: - PATH - LIBPATH - LIB - INCLUDE - Platform - FrameworkDir - FrameworkVersion - FrameworkDIR32 - FrameworkVersion32 - FrameworkDIR64 - FrameworkVersion64 - VCToolsRedistDir - VCINSTALLDIR - VCToolsInstallDir - VCToolsVersion - WindowsLibPath - WindowsSdkDir - WindowsSDKVersion - WindowsSdkBinPath - WindowsSdkVerBinPath - WindowsSDKLibVersion - __DOTNET_ADD_32BIT - __DOTNET_ADD_64BIT - __DOTNET_PREFERRED_BITNESS - Framework{framework version}Version - NETFXSDKDir - UniversalCRTSdkDir - UCRTVersion - ExtensionSdkDir - - These last 2 are set to ensure that distuils uses these environment - variables when compiling libopenzwave.pyd - MSSDK - DISTUTILS_USE_SDK - - :return: dict of environment variables - """ - path = os.environ.get('Path', '') - - env = dict( - __VSCMD_PREINIT_PATH=path, - Platform=self.platform, - VSCMD_ARG_app_plat='Desktop', - VSCMD_ARG_HOST_ARCH=self.platform, - VSCMD_ARG_TGT_ARCH=self.platform, - __VSCMD_script_err_count='0' - ) - - env_path = set() - - def update_env(cls): - for key, value in cls: - if key == 'Path': - for item in value.split(os.pathsep): - env_path.add(item) - continue - - if key in env: - env[key] += os.pathsep + value - else: - env[key] = value - - update_env(self.visual_c) - update_env(self.visual_studio) - update_env(self.windows_sdk) - update_env(self.dot_net) - - env_path = os.pathsep.join(item for item in env_path) - env['Path'] = env_path - - return env - - def __str__(self): - template = ( - 'Machine architecture: {machine_architecture}\n' - 'Build architecture: {architecture}\n' - ) - - res = [ - template.format( - machine_architecture=self.machine_architecture, - architecture=self.platform - ), - str(self.python), - str(self.visual_studio), - str(self.visual_c), - str(self.windows_sdk), - str(self.dot_net), - ] - - return '\n'.join(res) - - -def setup_environment( - minimum_c_version: Optional[Union[int, float]] = None, - strict_c_version: Optional[Union[int, float]] = None, - minimum_toolkit_version: Optional[int] = None, - strict_toolkit_version: Optional[int] = None, - minimum_sdk_version: Optional[str] = None, - strict_sdk_version: Optional[str] = None, - minimum_net_version: Optional[str] = None, - strict_net_version: Optional[str] = None, - vs_version: Optional[Union[str, int]] = None -): - """ - Main entry point. - - :param minimum_c_version: The lowest MSVC compiler version to allow. - :type minimum_c_version: optional - int, float - - :param strict_c_version: The MSVC compiler version that MUST be used. - ie 14.0, 14.2 - :type strict_c_version: optional - int or float - - :param minimum_toolkit_version: The lowest build tools version to allow. - :type minimum_toolkit_version: optional - int - - :param strict_toolkit_version: The build tools version that MUST be used. - ie 142, 143 - :type strict_toolkit_version: optional - int - - :param minimum_sdk_version: The lowest SDK version to allow. - This can work several ways, if you want to specify a specific version as - the minimum `"10.0.22000.0"` or if you want to make sure that only - Windows 10 SDK's get used `"10.0"` - :type minimum_sdk_version: optional - str - - :param strict_sdk_version: The Windows SDK that MUST be used. - Whole version only. - :type strict_sdk_version: optional - str - - :param minimum_net_version: Works the same as minimum_sdk_version - :type minimum_net_version: optional - str - - :param strict_net_version: works the same as strict_sdk_version - :type strict_net_version: optional - str - - :param vs_version: The version of visual studio you want to use. - This can be one of the following version types. - - * version: `str("16.10.31515.178")` - * display version:`str("16.10.4")` - * product line version: `int(2019)`. - - If you have 2 installations that share the same product line version - the installation with the higher version will get used. An example of - this is Visual Studio 20019 and Build Tools 2019. If you want to specify - a specific installation in this case then use the version or - display version options. - :type vs_version: optional - str, int - - :return: Environment instance - :rtype: Environment - """ - - if not _IS_WIN: - raise RuntimeError( - 'This script will only work with a Windows opperating system.' - ) - - distutils.log.debug( - 'Setting up Windows build environment, please wait.....' - ) - - python_version = sys.version_info[:2] - if minimum_c_version is None: - if python_version == (3, 10): - minimum_c_version = 14.2 - elif python_version == (3, 9): - minimum_c_version = 14.2 - elif python_version == (3, 8): - minimum_c_version = 14.0 - elif python_version == (3, 7): - minimum_c_version = 14.0 - elif python_version == (3, 6): - minimum_c_version = 14.0 - elif python_version == (3, 5): - minimum_c_version = 12.0 - elif python_version == (3, 4): - minimum_c_version = 12.0 - else: - raise RuntimeError( - 'ozw does not support this version of python' - ) - - environment = Environment( - minimum_c_version, - strict_c_version, - minimum_toolkit_version, - strict_toolkit_version, - minimum_sdk_version, - strict_sdk_version, - minimum_net_version, - strict_net_version, - vs_version - ) - - distutils.log.debug('\n' + str(environment) + '\n\n') - distutils.log.debug('SET ENVIRONMENT VARIABLES') - distutils.log.debug('------------------------------------------------') - distutils.log.debug('\n') - - for key, value in environment.build_environment.items(): - old_val = os.environ.get(key, value) - if old_val != value: - if os.pathsep in old_val or os.pathsep in value: - value = [item for item in set(value.split(os.pathsep))] - old_val = [ - item for item in set(old_val.split(os.pathsep)) - if item not in value - ] - - value.extend(old_val) - value = os.pathsep.join( - item.strip() for item in value if item.strip() - ) - - distutils.log.debug(key + '=' + value) - os.environ[key] = value - - distutils.log.debug('\n\n') - - return environment - - -if __name__ == '__main__': - distutils.log.set_threshold(distutils.log.DEBUG) - - # build tools 2019 '16.10.31515.178' '16.10.4' - # visual studio 2019 '16.11.31729.503' '16.11.5' - - envr = setup_environment() # vs_version='16.10.4') - print() - print() - print('SET ENVIRONMENT VARIABLES') - print('------------------------------------------------') - print() - for k in envr.build_environment.keys(): - v = os.environ[k] - print(k + '=', v) diff --git a/buildtools/msvc/vswhere.py b/buildtools/msvc/vswhere.py deleted file mode 100644 index f45ac05d07..0000000000 --- a/buildtools/msvc/vswhere.py +++ /dev/null @@ -1,2288 +0,0 @@ -# ############################################################################# -# -# MIT License -# -# Copyright 2022 Kevin G. Schlosser -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# ############################################################################# - - -# ############################################################################# -# -# This software is OSI Certified Open Source Software. -# OSI Certified is a certification mark of the Open Source Initiative. -# -# Copyright (c) 2006-2013, Thomas Heller. -# Copyright (c) 2014, Comtypes Developers. -# All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# ############################################################################# - -import weakref -import ctypes -import sys -import atexit -import datetime -from _ctypes import COMError -from ctypes import POINTER, HRESULT -from ctypes.wintypes import ( - LPCOLESTR, - ULONG, - USHORT, - LPCWSTR, - LPVOID, - LCID, - DWORD, - LONG, - WORD, - BYTE, - INT, - BOOL, - SHORT, - UINT, - FLOAT, - DOUBLE, - VARIANT_BOOL, -) - -UBYTE = ctypes.c_ubyte -ULONGLONG = ctypes.c_ulonglong -LONGLONG = ctypes.c_longlong -VARTYPE = USHORT -PVOID = ctypes.c_void_p -ENUM = ctypes.c_uint -PULONGLONG = POINTER(ctypes.c_ulonglong) - -_oleaut32 = ctypes.windll.oleaut32 -_ole32_nohresult = ctypes.windll.ole32 -_ole32 = ctypes.oledll.ole32 -_kernel32 = ctypes.windll.kernel32 - -_StringFromCLSID = _ole32.StringFromCLSID -_CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree -_CLSIDFromString = _ole32.CLSIDFromString -_VariantClear = _oleaut32.VariantClear - -_SafeArrayLock = _oleaut32.SafeArrayLock -_SafeArrayLock.restype = HRESULT - -_SafeArrayUnlock = _oleaut32.SafeArrayUnlock -_SafeArrayUnlock.restype = HRESULT - -_GetUserDefaultLCID = _kernel32.GetUserDefaultLCID -_GetUserDefaultLCID.restype = LCID - -_FileTimeToSystemTime = _kernel32.FileTimeToSystemTime -_FileTimeToSystemTime.restype = BOOL - -_SystemTimeToFileTime = _kernel32.SystemTimeToFileTime -_SystemTimeToFileTime.restype = BOOL - -ctypes.pythonapi.PyInstanceMethod_New.argtypes = [ctypes.py_object] -ctypes.pythonapi.PyInstanceMethod_New.restype = ctypes.py_object -PyInstanceMethod_Type = type(ctypes.pythonapi.PyInstanceMethod_New(id)) - -CLSCTX_SERVER = 5 -CLSCTX_ALL = 7 -COINIT_APARTMENTTHREADED = 0x2 -MAXUINT = 0xFFFFFFFF - -ERROR_FILE_NOT_FOUND = 0x00000002 -ERROR_NOT_FOUND = 0x00000490 - - -def HRESULT_FROM_WIN32(x): - return x - - -# Constants -E_NOTFOUND = HRESULT_FROM_WIN32(ERROR_NOT_FOUND) -E_FILENOTFOUND = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) - -VT_EMPTY = 0 -VT_NULL = 1 -VT_I2 = 2 -VT_I4 = 3 -VT_R4 = 4 -VT_R8 = 5 -VT_CY = 6 -VT_DATE = 7 -VT_BSTR = 8 -VT_BOOL = 11 -VT_I1 = 16 -VT_UI1 = 17 -VT_UI2 = 18 -VT_UI4 = 19 -VT_I8 = 20 -VT_UI8 = 21 -VT_INT = 22 -VT_UINT = 23 - - -_PARAMFLAGS = { - "in": 1, - "out": 2, - "lcid": 4, - "retval": 8, - "optional": 16, -} - - -def instancemethod(func, inst, _): - mth = PyInstanceMethod_Type(func) - if inst is None: - return mth - return mth.__get__(inst) - - -def CoInitialize(): - flags = getattr(sys, "coinit_flags", COINIT_APARTMENTTHREADED) - _ole32.CoInitializeEx(None, flags) - - -def CoUninitialize(): - _ole32_nohresult.CoUninitialize() - - -def CoCreateInstance(clsid, interface=None, clsctx=None, punkouter=None): - if clsctx is None: - clsctx = CLSCTX_SERVER - - if interface is None: - interface = IUnknown - - p = POINTER(interface)() - iid = interface._iid_ # NOQA - _ole32.CoCreateInstance( - ctypes.byref(clsid), - punkouter, - clsctx, - ctypes.byref(iid), - ctypes.byref(p) - ) - return p - - -def _shutdown( - func=_ole32_nohresult.CoUninitialize, - _exc_clear=getattr(sys, "exc_clear", lambda: None) -): - _exc_clear() - - try: - func() - except WindowsError: - pass - - if _cominterface_meta is not None: - _cominterface_meta._com_shutting_down = True - - -class ReferenceEmptyClass(object): - pass - - -class Patch(object): - def __init__(self, target): - self.target = target - - def __call__(self, patches): - for name, value in list(vars(patches).items()): - if name in vars(ReferenceEmptyClass): - continue - n_replace = getattr(value, '__no_replace', False) - if n_replace and hasattr(self.target, name): - continue - - setattr(self.target, name, value) - - -def no_replace(f): - f.__no_replace = True - return f - - -class tagSAFEARRAYBOUND(ctypes.Structure): - _fields_ = [ - ('cElements', DWORD), - ('lLbound', LONG), - ] - - -SAFEARRAYBOUND = tagSAFEARRAYBOUND - - -class tagSAFEARRAY(ctypes.Structure): - _fields_ = [ - ('cDims', USHORT), - ('fFeatures', USHORT), - ('cbElements', DWORD), - ('cLocks', DWORD), - ('pvData', PVOID), - ('rgsabound', SAFEARRAYBOUND * 1), - ] - - -SAFEARRAY = tagSAFEARRAY -LPSAFEARRAY = POINTER(SAFEARRAY) - -_SafeArrayLock.argtypes = [POINTER(SAFEARRAY)] -_SafeArrayUnlock.argtypes = [POINTER(SAFEARRAY)] - - -class BSTR(ctypes.c_wchar_p): - _needsfree = False - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, self.value) - - def __ctypes_from_outparam__(self): - self._needsfree = True - return self.value - - def __del__(self, _free=_oleaut32.SysFreeString): - if self._b_base_ is None or self._needsfree: # NOQA - _free(self) - - def from_param(cls, value): - if isinstance(value, cls): - return value - - return cls(value) - - from_param = classmethod(from_param) - - -class GUID(ctypes.Structure): - _fields_ = [ - ("Data1", DWORD), - ("Data2", WORD), - ("Data3", WORD), - ("Data4", BYTE * 8) - ] - - def __init__(self, name=None): - ctypes.Structure.__init__(self) - - if name is not None: - _CLSIDFromString(str(name), ctypes.byref(self)) - - def __repr__(self): - return 'GUID("%s")' % str(self) - - def __str__(self): - p = ctypes.c_wchar_p() - _StringFromCLSID(ctypes.byref(self), ctypes.byref(p)) - result = p.value - _CoTaskMemFree(p) - return result - - def __eq__(self, other): - # noinspection PyTypeChecker - return isinstance(other, GUID) and bytes(self) == bytes(other) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - # noinspection PyTypeChecker - return hash(bytes(self)) - - -IID = GUID -CLSID = GUID - - -def _encode_idl(names): - # sum up all values found in _PARAMFLAGS, ignoring all others. - return sum([_PARAMFLAGS.get(n, 0) for n in names]) - - -def COMMETHOD(idlflags, restype, methodname, *argspec): - paramflags = [] - argtypes = [] - - for item in argspec: - idl, typ, argname = item - pflags = _encode_idl(idl) - paramflags.append((pflags, argname)) - argtypes.append(typ) - - return ( - restype, - methodname, - tuple(argtypes), - tuple(paramflags), - tuple(idlflags), - None - ) - - -com_interface_registry = {} - - -class _cominterface_meta(type): - _com_shutting_down = False - - def __new__(self, name, bases, namespace): # NOQA - methods = namespace.pop("_methods_", None) - cls = type.__new__(self, name, bases, namespace) - - if methods is not None: - cls._methods_ = methods - - if bases == (object,): - _ptr_bases = (cls, _compointer_base) - else: - _ptr_bases = (cls, POINTER(bases[0])) - - p = type(_compointer_base)( - "POINTER(%s)" % cls.__name__, - _ptr_bases, - { - "__com_interface__": cls, - "_needs_com_addref_": None - } - ) - - from ctypes import _pointer_type_cache # NOQA - _pointer_type_cache[cls] = p - - @Patch(POINTER(p)) - class ReferenceFix(object): # NOQA - def __setitem__(self, index, value): - if index != 0: - if bool(value): - value.AddRef() - - super(POINTER(p), self).__setitem__(index, value) # NOQA - return - - from _ctypes import CopyComPointer - CopyComPointer(value, self) - - return cls - - def __setattr__(self, name, value): - if name == "_methods_": - self._make_methods(value) - - type.__setattr__(self, name, value) - - def __get_baseinterface_methodcount(self): - itf_name = None - try: - result = 0 - for itf in self.mro()[1:-1]: - itf_name = itf.__name__ - result += len(itf.__dict__["_methods_"]) - return result - - except KeyError as err: - (name,) = err.args - if name == "_methods_": - raise TypeError( - "baseinterface '%s' has no _methods_" % itf_name - ) - raise - - def _fix_inout_args(self, func, argtypes, paramflags): # NOQA - SIMPLETYPE = type(INT) - BYREFTYPE = type(ctypes.byref(INT())) - - def call_with_inout(self_, *args, **kw): - args = list(args) - outargs = {} - outnum = 0 - for i, info in enumerate(paramflags): - direction = info[0] - if direction & 3 == 3: - name = info[1] - atyp = argtypes[i]._type_ # NOQA - - try: - try: - v = args[i] - except IndexError: - v = kw[name] - except KeyError: - v = atyp() - else: - if getattr(v, "_type_", None) is atyp: - pass - elif type(atyp) is SIMPLETYPE: - v = atyp(v) - else: - v = atyp.from_param(v) - assert not isinstance(v, BYREFTYPE) - outargs[outnum] = v - outnum += 1 - if len(args) > i: - args[i] = v - else: - kw[name] = v - elif direction & 2 == 2: - outnum += 1 - - rescode = func(self_, *args, **kw) - - if outnum == 1: - if len(outargs) == 1: - rescode = rescode.__ctypes_from_outparam__() - return rescode - - rescode = list(rescode) - for outnum, o in list(outargs.items()): - rescode[outnum] = o.__ctypes_from_outparam__() - return rescode - - return call_with_inout - - def _make_methods(self, methods): - iid = self.__dict__["_iid_"] - - iid = str(iid) - com_interface_registry[iid] = self - del iid - - vtbl_offset = self.__get_baseinterface_methodcount() - - for i, item in enumerate(methods): - restype, name, argtypes, paramflags, idlflags, doc = item - prototype = ctypes.WINFUNCTYPE(restype, *argtypes) - - if restype == HRESULT: - # noinspection PyTypeChecker - raw_func = prototype( - i + vtbl_offset, - name, - None, - self._iid_ # NOQA - ) - - func = prototype( - i + vtbl_offset, - name, - paramflags, - self._iid_ # NOQA - ) - else: - # noinspection PyTypeChecker - raw_func = prototype(i + vtbl_offset, name, None, None) - # noinspection PyTypeChecker - func = prototype(i + vtbl_offset, name, paramflags, None) - - setattr( - self, - "_%s__com_%s" % (self.__name__, name), - instancemethod(raw_func, None, self) - ) - - if paramflags: - dirflags = [(p[0] & 3) for p in paramflags] - if 3 in dirflags: - func = self._fix_inout_args(func, argtypes, paramflags) - - func.__doc__ = doc - func.__name__ = name - - mth = instancemethod(func, None, self) - - if hasattr(self, name): - setattr(self, "_" + name, mth) - else: - setattr(self, name, mth) - - -class _compointer_meta(type(ctypes.c_void_p), _cominterface_meta): - pass - - -class _compointer_base(ctypes.c_void_p, metaclass=_compointer_meta): - - def __del__(self): - if self: - if not type(self)._com_shutting_down: # NOQA - self.Release() # NOQA - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - if not isinstance(other, _compointer_base): - return False - - val1 = super(_compointer_base, self).value - val2 = super(_compointer_base, other).value - - return val1 == val2 - - def __hash__(self): - return hash(super(_compointer_base, self).value) - - def __get_value(self): - return self - - value = property(__get_value, doc="""Return self.""") - - def __repr__(self): - ptr = super(_compointer_base, self).value - return "<%s ptr=0x%x at %x>" % ( - self.__class__.__name__, - ptr or 0, - id(self) - ) - - def from_param(cls, value): - if value is None: - return None - if value == 0: - return None - if isinstance(value, cls): - return value - - if cls._iid_ == getattr(value, "_iid_", None): # NOQA - return value - - try: - table = value._com_pointers_ # NOQA - except AttributeError: - pass - else: - try: - return table[cls._iid_] # NOQA - except KeyError: - raise TypeError( - "Interface %s not supported" % cls._iid_ # NOQA - ) - - return value.QueryInterface(cls.__com_interface__) # NOQA - - from_param = classmethod(from_param) - - -class IUnknown(object, metaclass=_cominterface_meta): - _case_insensitive_ = False - _iid_ = GUID("{00000000-0000-0000-C000-000000000046}") - - _methods_ = [ - COMMETHOD( - [], - HRESULT, - "QueryInterface", - (['in'], POINTER(GUID), "riid"), - (['in'], POINTER(PVOID), "ppvObject") - ), - COMMETHOD( - [], - ULONG, - "AddRef" - ), - COMMETHOD( - [], - ULONG, - "Release" - ) - ] - - def QueryInterface(self, interface, iid=None): - p = POINTER(interface)() - - if iid is None: - iid = interface._iid_ # NOQA - - self.__com_QueryInterface(ctypes.byref(iid), ctypes.byref(p)) # NOQA - - clsid = self.__dict__.get('__clsid') - if clsid is not None: - p.__dict__['__clsid'] = clsid - - return p - - def AddRef(self): - return self.__com_AddRef() # NOQA - - def Release(self): - return self.__com_Release() # NOQA - - -class tagDEC(ctypes.Structure): - _fields_ = [ - ("wReserved", ctypes.c_ushort), - ("scale", ctypes.c_ubyte), - ("sign", ctypes.c_ubyte), - ("Hi32", ctypes.c_ulong), - ("Lo64", ctypes.c_ulonglong) - ] - - -DECIMAL = tagDEC - - -class _FILETIME(ctypes.Structure): - _fields_ = [ - ('dwLowDateTime', DWORD), - ('dwHighDateTime', DWORD) - ] - - @property - def value(self): - system_time = SYSTEMTIME() - _FileTimeToSystemTime(ctypes.byref(self), ctypes.byref(system_time)) - return system_time.value - - @value.setter - def value(self, dt): - system_time = SYSTEMTIME() - system_time.value = dt - _SystemTimeToFileTime(ctypes.byref(system_time), ctypes.byref(self)) - - -FILETIME = _FILETIME -LPFILETIME = POINTER(FILETIME) - - -class _SYSTEMTIME(ctypes.Structure): - _fields_ = [ - ('wYear', WORD), - ('wMonth', WORD), - ('wDayOfWeek', WORD), - ('wDay', WORD), - ('wHour', WORD), - ('wMinute', WORD), - ('wSecond', WORD), - ('wMilliseconds', WORD), - ] - - @property - def value(self): - dt = datetime.datetime( - year=self.wYear, - month=self.wMonth, - day=self.wDay, - hour=self.wHour, - minute=self.wMinute, - second=self.wSecond, - microsecond=self.wMilliseconds * 1000 - ) - - return dt - - # noinspection PyAttributeOutsideInit - @value.setter - def value(self, dt): - if isinstance(dt, (int, float)): - dt = datetime.datetime.fromtimestamp(dt) - - weekday = dt.weekday() + 1 - if weekday == 7: - weekday = 0 - - self.wYear = dt.year - self.wMonth = dt.month - self.wDayOfWeek = weekday - self.wDay = dt.day - self.wHour = dt.hour - self.wMinute = dt.minute - self.wSecond = dt.second - self.wMilliseconds = int(dt.microsecond / 1000) - - -SYSTEMTIME = _SYSTEMTIME - - -class tagVARIANT(ctypes.Structure): - class U_VARIANT1(ctypes.Union): - class __tagVARIANT(ctypes.Structure): - class U_VARIANT2(ctypes.Union): - class _tagBRECORD(ctypes.Structure): - # noinspection PyTypeChecker - _fields_ = [ - ("pvRecord", PVOID), - ("pRecInfo", POINTER(IUnknown)) - ] - - _fields_ = [ - ("VT_BOOL", VARIANT_BOOL), - ("VT_I1", BYTE), - ("VT_I2", SHORT), - ("VT_I4", LONG), - ("VT_I8", LONGLONG), - ("VT_INT", INT), - ("VT_UI1", UBYTE), - ("VT_UI2", USHORT), - ("VT_UI4", ULONG), - ("VT_UI8", ULONGLONG), - ("VT_UINT", UINT), - ("VT_R4", FLOAT), - ("VT_R8", DOUBLE), - ("VT_CY", LONGLONG), - ("c_wchar_p", ctypes.c_wchar_p), - ("c_void_p", PVOID), - ("pparray", POINTER(POINTER(tagSAFEARRAY))), - ("bstrVal", BSTR), - ("_tagBRECORD", _tagBRECORD), - ] - _anonymous_ = ["_tagBRECORD"] - - _fields_ = [ - ("vt", VARTYPE), - ("wReserved1", USHORT), - ("wReserved2", USHORT), - ("wReserved3", USHORT), - ("_", U_VARIANT2) - ] - - _fields_ = [ - ("__VARIANT_NAME_2", __tagVARIANT), - ("decVal", DECIMAL) - ] - _anonymous_ = ["__VARIANT_NAME_2"] - - _fields_ = [ - ("__VARIANT_NAME_1", U_VARIANT1) - ] - _anonymous_ = ["__VARIANT_NAME_1"] - - def __init__(self): - ctypes.Structure.__init__(self) - - def __del__(self): - if self._b_needsfree_: - _VariantClear(self) - - @property - def value(self): - vt = self.vt - if vt in (VT_EMPTY, VT_NULL): - return None - elif vt == VT_I1: - return self._.VT_I1 - elif vt == VT_I2: - return self._.VT_I2 - elif vt == VT_I4: - return self._.VT_I4 - elif vt == VT_I8: - return self._.VT_I8 - elif vt == VT_UI8: - return self._.VT_UI8 - elif vt == VT_INT: - return self._.VT_INT - elif vt == VT_UI1: - return self._.VT_UI1 - elif vt == VT_UI2: - return self._.VT_UI2 - elif vt == VT_UI4: - return self._.VT_UI4 - elif vt == VT_UINT: - return self._.VT_UINT - elif vt == VT_R4: - return self._.VT_R4 - elif vt == VT_R8: - return self._.VT_R8 - elif vt == VT_BOOL: - return self._.VT_BOOL - elif vt == VT_BSTR: - return self._.bstrVal - - def __ctypes_from_outparam__(self): - result = self.value - self.vt = VT_EMPTY - return result - - -LPVARIANT = POINTER(tagVARIANT) -VARIANT = tagVARIANT - -_VariantClear.argtypes = (POINTER(VARIANT),) - - -# Enumerations -# The state of an instance. -class InstanceState(ENUM): - # The instance state has not been determined. - eNone = 0 - # The instance installation path exists. - eLocal = 1 - # A product is registered to the instance. - eRegistered = 2 - # No reboot is required for the instance. - eNoRebootRequired = 4 - # do not know what this bit does - eUnknown = 8 - # The instance represents a complete install. - eComplete = MAXUINT - - @property - def value(self): - # noinspection PyUnresolvedReferences - value = ENUM.value.__get__(self) - if value == self.eComplete: - return ['local', 'registered', 'no reboot required'] - if value == self.eNone: - return ['remote', 'unregistered', 'reboot required'] - - res = [] - - if value | self.eLocal == value: - res += ['local'] - else: - res += ['remote'] - if value | self.eRegistered == value: - res += ['registered'] - else: - res += ['unregistered'] - if value | self.eNoRebootRequired == value: - res += ['no reboot required'] - else: - res += ['reboot required'] - - if value | self.eUnknown == value: - res.append('unknown flag set') - - return res - - -eNone = InstanceState.eNone -eLocal = InstanceState.eLocal -eRegistered = InstanceState.eRegistered -eNoRebootRequired = InstanceState.eNoRebootRequired -eUnknown = InstanceState.eUnknown -eComplete = InstanceState.eComplete - - -class Packages(object): - - def __init__(self, packages): - self._packages = packages - - def __iter__(self): - return iter(self._packages) - - def __str__(self): - res = [] - - def _add(items): - for item in items: - res.append('\n'.join( - ' ' + line - for line in str(item).split('\n') - )) - res.append('') - - res.append('vsix:') - _add(self.vsix) - res.append('group:') - _add(self.group) - res.append('component:') - _add(self.component) - res.append('workload:') - _add(self.workload) - res.append('product:') - _add(self.product) - res.append('msi:') - _add(self.msi) - res.append('exe:') - _add(self.exe) - res.append('msu:') - _add(self.msu) - res.append('other:') - _add(self.other) - - return '\n'.join(res) - - @property - def vsix(self): - return [ - package for package in self - if package.type == 'Vsix' - ] - - @property - def group(self): - return [ - package for package in self - if package.type == 'Group' - ] - - @property - def component(self): - return [ - package for package in self - if package.type == 'Component' - ] - - @property - def workload(self): - return [ - package for package in self - if package.type == 'Workload' - ] - - @property - def product(self): - return [ - package for package in self - if package.type == 'Product' - ] - - @property - def msi(self): - return [ - package for package in self - if package.type == 'Msi' - ] - - @property - def exe(self): - return [ - package for package in self - if package.type == 'Exe' - ] - - @property - def msu(self): - return [ - package for package in self - if package.type == 'Msu' - ] - - @property - def other(self): - return [ - package for package in self - if package.type not in ( - 'Exe', 'Msi', 'Product', 'Vsix', - 'Group', 'Component', 'Workload', 'Msu' - ) - ] - - -# Forward declarations - - -IID_ISetupPackageReference = IID("{da8d8a16-b2b6-4487-a2f1-594ccccd6bf5}") - - -# A reference to a package. -class ISetupPackageReference(IUnknown): - _iid_ = IID_ISetupPackageReference - - def __gt__(self, other): - if isinstance(other, ISetupPackageReference): - return self.version > other.version - - other = _convert_version(other) - - if not isinstance(other, tuple): - return False - - return self.version > other - - def __lt__(self, other): - if isinstance(other, ISetupPackageReference): - return self.version < other.version - - other = _convert_version(other) - - if not isinstance(other, str): - return False - - return self.version < other - - def __ge__(self, other): - if isinstance(other, ISetupPackageReference): - return self.version >= other.version - - other = _convert_version(other) - - if not isinstance(other, str): - return False - - return self.version >= other - - def __le__(self, other): - if isinstance(other, ISetupPackageReference): - return self.version <= other.version - - other = _convert_version(other) - - if not isinstance(other, str): - return False - - return self.version <= other - - def __eq__(self, other): - if isinstance(other, ISetupPackageReference): - return self.version == other.version - - other = _convert_version(other) - - if not isinstance(other, str): - return object.__eq__(self, other) - - return self.version == other - - def __ne__(self, other): - return not self.__eq__(other) - - @property - def name(self): - # noinspection PyUnresolvedReferences - return self.GetId() - - @property - def id(self): - # noinspection PyUnresolvedReferences - return self.GetId() - - @property - def version(self): - # noinspection PyUnresolvedReferences - return self.GetVersion() - - @property - def chip(self): - # noinspection PyUnresolvedReferences - return self.GetChip() - - @property - def language(self): - # noinspection PyUnresolvedReferences - return self.GetLanguage() - - @property - def branch(self): - # noinspection PyUnresolvedReferences - return self.GetBranch() - - @property - def type(self): - # noinspection PyUnresolvedReferences - return self.GetType() - - @property - def unique_id(self): - # noinspection PyUnresolvedReferences - return self.GetUniqueId() - - @property - def is_extension(self): - # noinspection PyUnresolvedReferences - return self.GetIsExtension() - - def __str__(self): - res = [ - 'id: ' + str(self.id), - 'version: ' + str(self.version), - 'chip: ' + str(self.chip), - 'language: ' + str(self.language), - 'branch: ' + str(self.branch), - 'type: ' + str(self.type), - 'unique id: ' + str(self.unique_id), - 'is extension: ' + str(self.is_extension) - ] - return '\n'.join(res) - - -IID_ISetupInstance = IID("{B41463C3-8866-43B5-BC33-2B0676F7F42E}") - - -def _convert_version(other): - if isinstance(other, str): - other = tuple(int(item) for item in other.split('.')) - elif isinstance(other, bytes): - other = tuple( - int(item) for item in other.decode('utf-8').split('.') - ) - elif isinstance(other, list): - other = tuple(other) - elif isinstance(other, int): - other = (other,) - elif isinstance(other, float): - other = tuple(int(item) for item in str(other).split('.')) - - if isinstance(other, tuple): - other = '.'.join(str(item) for item in other) - - return other - - -# Information about an instance of a product. -class ISetupInstance(IUnknown): - _iid_ = IID_ISetupInstance - _helper = None - - def __call__(self, helper): - self._helper = helper - return self - - @property - def id(self): - # noinspection PyUnresolvedReferences - return self.GetInstanceId() - - @property - def install_date(self): - # noinspection PyUnresolvedReferences - return self.GetInstallDate().value - - @property - def name(self): - # noinspection PyUnresolvedReferences - return self.GetInstallationName() - - @property - def path(self): - # noinspection PyUnresolvedReferences - return self.GetInstallationPath() - - @property - def version(self): - # noinspection PyUnresolvedReferences - return self.GetInstallationVersion() - - @property - def full_version(self): - if self._helper is not None: - return self._helper.ParseVersion(self.version) - - @property - def display_name(self): - try: - # noinspection PyUnresolvedReferences - return self.GetDisplayName(_GetUserDefaultLCID()) - except (OSError, ValueError, COMError): - pass - - @property - def description(self): - try: - # noinspection PyUnresolvedReferences - return self.GetDescription(_GetUserDefaultLCID()) - except (OSError, ValueError, COMError): - pass - - def __str__(self): - title_bar = '-- ' + str(self.display_name) + ' ' - title_bar += '-' * (63 - len(title_bar)) - res = [ - title_bar, - 'description: ' + str(self.description), - 'version: ' + str(self.version), - 'id: ' + str(self.id), - 'name: ' + str(self.name), - 'path: ' + str(self.path), - 'full version: ' + str(self.full_version), - 'install date: ' + self.install_date.strftime('%c') - ] - return '\n'.join(res) - - -IID_ISetupInstance2 = IID("{89143C9A-05AF-49B0-B717-72E218A2185C}") - - -# Information about an instance of a product. -class ISetupInstance2(ISetupInstance): - _iid_ = IID_ISetupInstance2 - - @property - def packages(self) -> Packages: - # noinspection PyUnresolvedReferences - safearray = self.GetPackages() - - _SafeArrayLock(safearray) - - # noinspection PyTypeChecker - packs = ctypes.cast( - safearray.contents.pvData, - POINTER(POINTER(ISetupPackageReference)) - ) - - cPackages = safearray.contents.rgsabound[0].cElements - - res = [] - for i in range(cPackages): - p = packs[i] - res.append(p) - - _SafeArrayUnlock(safearray) - res = Packages(res) - return res - - @property - def properties(self): - # noinspection PyUnresolvedReferences - return self.GetProperties() - - @property - def product(self): - """ - version - chip - language - branch - type - unique_id - is_extension - """ - if 'registered' in self.state: - # noinspection PyUnresolvedReferences - return self.GetProduct() - - @property - def state(self): - # noinspection PyUnresolvedReferences - return self.GetState().value - - @property - def product_path(self): - # noinspection PyUnresolvedReferences - return self.GetProductPath() - - @property - def errors(self): - # noinspection PyUnresolvedReferences - errors = self.GetErrors() - try: - return errors.QueryInterface(ISetupErrorState2) - except ValueError: - return errors - - @property - def is_launchable(self): - # noinspection PyUnresolvedReferences - return self.IsLaunchable() - - @property - def is_complete(self): - # noinspection PyUnresolvedReferences - return self.IsComplete() - - @property - def is_prerelease(self): - catalog = self.QueryInterface(ISetupInstanceCatalog) - return catalog.IsPrerelease() # NOQA - - @property - def catalog(self): - return self.QueryInterface(ISetupInstanceCatalog) - - @property - def engine_path(self): - # noinspection PyUnresolvedReferences - return self.GetEnginePath() - - @property - def localised_properties(self): - return self.QueryInterface(ISetupLocalizedProperties) - - def __str__(self): - res = [ - ISetupInstance.__str__(self), - 'product path: ' + str(self.product_path), - 'is launchable: ' + str(self.is_launchable), - 'is complete: ' + str(self.is_complete), - 'is prerelease: ' + str(self.is_prerelease), - 'state: ' + str(self.state), - 'engine path: ' + str(self.engine_path), - 'errors:', - '{errors}', - 'product: ', - '{product}', - 'packages:', - '{packages}', - 'properties:', - '{properties}', - 'catalog:', - '{catalog}', - '-' * 63 - ] - - res = '\n'.join(res) - - return res.format( - errors='\n'.join( - ' ' + line - for line in str(self.errors).split('\n') - ), - product='\n'.join( - ' ' + line - for line in str(self.product).split('\n') - ), - packages='\n'.join( - ' ' + line - for line in str(self.packages).split('\n') - ), - properties='\n'.join( - ' ' + line - for line in str(self.properties).split('\n') - ), - catalog='\n'.join( - ' ' + line - for line in str(self.catalog).split('\n') - ) - ) - - -IID_ISetupInstanceCatalog = IID("{9AD8E40F-39A2-40F1-BF64-0A6C50DD9EEB}") - - -# Information about a catalog used to install an instance. -class ISetupInstanceCatalog(IUnknown): - _iid_ = IID_ISetupInstanceCatalog - - @property - def id(self): - for prop in self: - if prop.name == 'id': - return prop.value - - @property - def build_branch(self): - for prop in self: - if prop.name == 'buildBranch': - return prop.value - - @property - def build_version(self): - for prop in self: - if prop.name == 'buildVersion': - return prop.value - - @property - def local_build(self): - for prop in self: - if prop.name == 'localBuild': - return prop.value - - @property - def manifest_name(self): - for prop in self: - if prop.name == 'manifestName': - return prop.value - - @property - def manifest_type(self): - for prop in self: - if prop.name == 'manifestType': - return prop.value - - @property - def product_display_version(self): - for prop in self: - if prop.name == 'productDisplayVersion': - return prop.value - - @property - def product_line(self): - for prop in self: - if prop.name == 'productLine': - return prop.value - - @property - def product_line_version(self): - for prop in self: - if prop.name == 'productLineVersion': - return prop.value - - @property - def product_milestone(self): - for prop in self: - if prop.name == 'productMilestone': - return prop.value - - @property - def product_milestone_is_prerelease(self): - for prop in self: - if prop.name == 'productMilestoneIsPreRelease': - return prop.value - - @property - def product_name(self): - for prop in self: - if prop.name == 'productName': - return prop.value - - @property - def product_patch_version(self): - for prop in self: - if prop.name == 'productPatchVersion': - return prop.value - - @property - def product_prerelease_milestone_suffix(self): - for prop in self: - if prop.name == 'productPreReleaseMilestoneSuffix': - return prop.value - - @property - def product_semantic_version(self): - for prop in self: - if prop.name == 'productSemanticVersion': - return prop.value - - def __iter__(self): - # noinspection PyUnresolvedReferences - for prop in self.GetCatalogInfo(): - yield prop - - def __str__(self): - res = [prop.name + ': ' + str(prop.value) for prop in self] - return '\n'.join(res) - - -IID_ISetupLocalizedProperties = IID("{F4BD7382-FE27-4AB4-B974-9905B2A148B0}") - - -# Provides localized properties of an instance of a product. -class ISetupLocalizedProperties(IUnknown): - _iid_ = IID_ISetupLocalizedProperties - - -IID_IEnumSetupInstances = IID("{6380BCFF-41D3-4B2E-8B2E-BF8A6810C848}") - - -# A enumerator of installed ISetupInstance objects. -class IEnumSetupInstances(IUnknown): - _iid_ = IID_IEnumSetupInstances - - def __iter__(self): - while True: - try: - # noinspection PyUnresolvedReferences - set_instance, num = self.Next(1) - yield set_instance - - except COMError: - break - - -IID_ISetupConfiguration = IID("{42843719-DB4C-46C2-8E7C-64F1816EFD5B}") - - -# Gets information about product instances set up on the machine. -class ISetupConfiguration(IUnknown): - _iid_ = IID_ISetupConfiguration - - def __call__(self): - try: - return self.QueryInterface(ISetupConfiguration2) - except (ValueError, OSError, COMError): - return self - - def __iter__(self): - # noinspection PyUnresolvedReferences - setup_enum = self.EnumInstances() - helper = self.QueryInterface(ISetupHelper) - - for si in setup_enum: - if not si: - break - - yield si(helper) - - def __str__(self): - res = [] - for instance_config in self: - res += [str(instance_config)] - - return '\n\n\n'.join(res) - - -IID_ISetupConfiguration2 = IID("{26AAB78C-4A60-49D6-AF3B-3C35BC93365D}") - - -# Gets information about product instances. -class ISetupConfiguration2(ISetupConfiguration): - _iid_ = IID_ISetupConfiguration2 - - def __iter__(self): - # noinspection PyUnresolvedReferences - setup_enum = self.EnumAllInstances() - helper = self.QueryInterface(ISetupHelper) - - for si in setup_enum: - if not si: - break - - yield si.QueryInterface(ISetupInstance2)(helper) - - -IID_ISetupHelper = IID("{42b21b78-6192-463e-87bf-d577838f1d5c}") - - -class ISetupHelper(IUnknown): - _iid_ = IID_ISetupHelper - - -IID_ISetupErrorState = IID("{46DCCD94-A287-476A-851E-DFBC2FFDBC20}") - - -# Information about the error state of an instance. -class ISetupErrorState(IUnknown): - _iid_ = IID_ISetupErrorState - - @property - def failed_packages(self) -> Packages: - try: - # noinspection PyUnresolvedReferences - safearray = self.GetFailedPackages() - except ValueError: - return Packages([]) - - _SafeArrayLock(safearray) - - # noinspection PyTypeChecker - packs = ctypes.cast( - safearray.contents.pvData, - POINTER(POINTER(ISetupFailedPackageReference)) - ) - - cPackages = safearray.contents.rgsabound[0].cElements - - res = [] - for i in range(cPackages): - p = packs[i] - p = p.QueryInterface(ISetupFailedPackageReference2) - res.append(p) - - _SafeArrayUnlock(safearray) - res = Packages(res) - return res - - @property - def skipped_packages(self) -> Packages: - try: - # noinspection PyUnresolvedReferences - safearray = self.GetSkippedPackages() - except ValueError: - return Packages([]) - - _SafeArrayLock(safearray) - - # noinspection PyTypeChecker - packs = ctypes.cast( - safearray.contents.pvData, - POINTER(POINTER(ISetupFailedPackageReference)) - ) - - cPackages = safearray.contents.rgsabound[0].cElements - - res = [] - for i in range(cPackages): - p = packs[i] - p = p.QueryInterface(ISetupFailedPackageReference2) - res.append(p) - - _SafeArrayUnlock(safearray) - res = Packages(res) - return res - - def __str__(self): - res = ['failed packages: '] - res.extend([ - ' ' + line for line in - str(self.failed_packages).split('\n') - ]) - - res += ['skipped packages: '] - res.extend([ - ' ' + line for line in - str(self.skipped_packages).split('\n') - ]) - - return '\n'.join(res) - - -IID_ISetupErrorState2 = IID("{9871385B-CA69-48F2-BC1F-7A37CBF0B1EF}") - - -# Information about the error state of an instance. -class ISetupErrorState2(ISetupErrorState): - _iid_ = IID_ISetupErrorState2 - - @property - def error_log_file_path(self): - # noinspection PyUnresolvedReferences - return self.GetErrorLogFilePath() - - @property - def log_file_path(self): - # noinspection PyUnresolvedReferences - return self.GetLogFilePath() - - def __str__(self): - res = [ - 'error log file path: ' + self.error_log_file_path, - 'log file path: ' + self.log_file_path, - ISetupErrorState.__str__(self) - ] - return '\n'.join(res) - - -IID_ISetupFailedPackageReference = IID( - "{E73559CD-7003-4022-B134-27DC650B280F}" - ) - - -# A reference to a failed package. -class ISetupFailedPackageReference(ISetupPackageReference): - _iid_ = IID_ISetupFailedPackageReference - - -IID_ISetupFailedPackageReference2 = IID( - "{0FAD873E-E874-42E3-B268-4FE2F096B9CA}" - ) - - -# A reference to a failed package. -class ISetupFailedPackageReference2(ISetupFailedPackageReference): - _iid_ = IID_ISetupFailedPackageReference2 - - -IID_ISetupPropertyStore = IID("{C601C175-A3BE-44BC-91F6-4568D230FC83}") - - -class Property(object): - - def __init__(self, name, value): - self._name = name - self._value = value - - @property - def name(self): - return self._name - - @property - def value(self): - return self._value - - def __str__(self): - return self.name + ': ' + str(self.value) - - -# Provides named properties. -class ISetupPropertyStore(IUnknown): - _iid_ = IID_ISetupPropertyStore - - @property - def names(self): - # noinspection PyUnresolvedReferences - safearray = self.GetNames() - - _SafeArrayLock(safearray) - - names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) - cPackages = safearray.contents.rgsabound[0].cElements - - res = [] - for i in range(cPackages): - res.append(names[i]) - - _SafeArrayUnlock(safearray) - - return res - - def __iter__(self): - for n in self.names: - # noinspection PyUnresolvedReferences - v = VARIANT() - - self.GetValue(n, ctypes.byref(v)) # NOQA - - v = v.value - if isinstance(v, BSTR): - v = v.value - - yield Property(n.value, v) - - def __str__(self): - return '\n'.join(str(prop) for prop in self) - - -IID_ISetupLocalizedPropertyStore = IID( - "{5BB53126-E0D5-43DF-80F1-6B161E5C6F6C}" - ) - - -# Provides localized named properties. -class ISetupLocalizedPropertyStore(IUnknown): - _iid_ = IID_ISetupLocalizedPropertyStore - - @property - def names(self): - # noinspection PyUnresolvedReferences - safearray = self.GetNames() - - _SafeArrayLock(safearray) - - names = ctypes.cast(safearray.contents.pvData, POINTER(BSTR)) - cPackages = safearray.contents.rgsabound[0].cElements - - res = [] - for i in range(cPackages): - res.append(names[i]) - - _SafeArrayUnlock(safearray) - - return res - - def __iter__(self): - for n in self.names: - # noinspection PyUnresolvedReferences - v = VARIANT() - - self.GetValue(n, ctypes.byref(v)) # NOQA - - v = v.value - if isinstance(v, BSTR): - v = v.value - - yield Property(n.value, v) - - def __str__(self): - return '\n'.join(str(prop) for prop in self) - - -ISetupPackageReference._methods_ = [ - # Gets the general package identifier. - COMMETHOD( - [], - HRESULT, - "GetId", - (['out'], POINTER(BSTR), "pbstrId") - ), - # Gets the version of the package. - COMMETHOD( - [], - HRESULT, - "GetVersion", - (['out'], POINTER(BSTR), "pbstrVersion") - - ), - # Gets the target process architecture of the package. - COMMETHOD( - [], - HRESULT, - "GetChip", - (['out'], POINTER(BSTR), "pbstrChip") - ), - # Gets the language and optional region identifier. - COMMETHOD( - [], - HRESULT, - "GetLanguage", - (['out'], POINTER(BSTR), "pbstrLanguage") - ), - # Gets the build branch of the package. - COMMETHOD( - [], - HRESULT, - "GetBranch", - (['out'], POINTER(BSTR), "pbstrBranch") - ), - # Gets the type of the package. - COMMETHOD( - [], - HRESULT, - "GetType", - (['out'], POINTER(BSTR), "pbstrType") - ), - # Gets the unique identifier consisting of all defined tokens. - COMMETHOD( - [], - HRESULT, - "GetUniqueId", - (['out'], POINTER(BSTR), "pbstrUniqueId") - ), - # Gets a value indicating whether the package refers to - # an external extension. - COMMETHOD( - [], - HRESULT, - "GetIsExtension", - (['out'], POINTER(VARIANT_BOOL), "pfIsExtension") - ) -] - -ISetupInstance._methods_ = [ - # Gets the instance identifier (should match the name of the - # parent instance directory). - COMMETHOD( - [], - HRESULT, - "GetInstanceId", - (['out'], POINTER(BSTR), "pbstrInstanceId") - ), - # Gets the local date and time when the installation - # was originally installed. - COMMETHOD( - [], - HRESULT, - "GetInstallDate", - (['out'], LPFILETIME, "pInstallDate") - ), - # Gets the unique name of the installation, often - # indicating the branch and other information used for telemetry. - COMMETHOD( - [], - HRESULT, - "GetInstallationName", - (['out'], POINTER(BSTR), "pbstrInstallationName") - ), - # Gets the path to the installation root of the product. - COMMETHOD( - [], - HRESULT, - "GetInstallationPath", - (['out'], POINTER(BSTR), "pbstrInstallationPath") - ), - # Gets the version of the product installed in this instance. - COMMETHOD( - [], - HRESULT, - "GetInstallationVersion", - (['out'], POINTER(BSTR), "pbstrInstallationVersion") - ), - # Gets the display name (title) of the product installed - # in this instance. - COMMETHOD( - [], - HRESULT, - "GetDisplayName", - (['in'], LCID, "lcid"), - (['out'], POINTER(BSTR), "pbstrDisplayName") - ), - # Gets the description of the product installed in this instance. - COMMETHOD( - [], - HRESULT, - "GetDescription", - (['in'], LCID, "lcid"), - (['out'], POINTER(BSTR), "pbstrDescription") - ), - # Resolves the optional relative path to the root path of the instance. - COMMETHOD( - [], - HRESULT, - "ResolvePath", - (['in'], LPCOLESTR, "pwszRelativePath"), - (['out'], POINTER(BSTR), "pbstrAbsolutePath") - - ) -] - -# noinspection PyTypeChecker -ISetupInstance2._methods_ = [ - # Gets the state of the instance. - COMMETHOD( - [], - HRESULT, - "GetState", - (['out'], POINTER(InstanceState), "pState") - ), - # Gets an array of package references registered to the instance. - COMMETHOD( - [], - HRESULT, - "GetPackages", - (['out'], POINTER(LPSAFEARRAY), "ppsaPackages") - ), - # Gets a pointer to the ISetupPackageReference that represents - # the registered product. - COMMETHOD( - [], - HRESULT, - "GetProduct", - (['out'], POINTER(POINTER(ISetupPackageReference)), "ppPackage") - ), - # Gets the relative path to the product application, if available. - COMMETHOD( - [], - HRESULT, - "GetProductPath", - (['out'], POINTER(BSTR), "pbstrProductPath") - ), - # Gets the error state of the instance, if available. - COMMETHOD( - [], - HRESULT, - "GetErrors", - (['out'], POINTER(POINTER(ISetupErrorState)), "ppErrorState") - ), - # Gets a value indicating whether the instance can be launched. - COMMETHOD( - [], - HRESULT, - "IsLaunchable", - (['out'], POINTER(VARIANT_BOOL), "pfIsLaunchable") - ), - # Gets a value indicating whether the instance is complete. - COMMETHOD( - [], - HRESULT, - "IsComplete", - (['out'], POINTER(VARIANT_BOOL), "pfIsComplete") - ), - # Gets product-specific properties. - COMMETHOD( - [], - HRESULT, - "GetProperties", - (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppProperties") - ), - # Gets the directory path to the setup engine - # that installed the instance. - COMMETHOD( - [], - HRESULT, - "GetEnginePath", - (['out'], POINTER(BSTR), "pbstrEnginePath") - ) -] - -# noinspection PyTypeChecker -ISetupInstanceCatalog._methods_ = [ - # Gets catalog information properties. - COMMETHOD( - [], - HRESULT, - "GetCatalogInfo", - (['out'], POINTER(POINTER(ISetupPropertyStore)), "ppCatalogInfo") - ), - # Gets a value indicating whether the catalog is a prerelease. - COMMETHOD( - [], - HRESULT, - "IsPrerelease", - (['out'], POINTER(VARIANT_BOOL), "pfIsPrerelease") - ) -] - -# noinspection PyTypeChecker -ISetupLocalizedProperties._methods_ = [ - # Gets localized product-specific properties. - COMMETHOD( - [], - HRESULT, - "GetLocalizedProperties", - ( - ['out'], - POINTER(POINTER(ISetupLocalizedPropertyStore)), - "ppLocalizedProperties" - ) - ), - # Gets localized channel-specific properties. - COMMETHOD( - [], - HRESULT, - "GetLocalizedChannelProperties", - ( - ['out'], - POINTER(POINTER(ISetupLocalizedPropertyStore)), - "ppLocalizedChannelProperties" - ) - ) -] - -# noinspection PyTypeChecker -IEnumSetupInstances._methods_ = [ - # Retrieves the next set of product instances in the - # enumeration sequence. - COMMETHOD( - [], - HRESULT, - "Next", - (['in'], ULONG, "celt"), - (['out'], POINTER(POINTER(ISetupInstance)), "rgelt"), - (['out'], POINTER(ULONG), "pceltFetched") - ), - # Skips the next set of product instances in the enumeration sequence. - COMMETHOD( - [], - HRESULT, - "Skip", - (['in'], ULONG, "celt") - ), - # Resets the enumeration sequence to the beginning. - COMMETHOD( - [], - HRESULT, - "Reset" - ), - # Creates a new enumeration object in the same state as the current - # enumeration object: the new object points to the same place in the - # enumeration sequence. - COMMETHOD( - [], - HRESULT, - "Clone", - (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppenum") - ) -] - -# noinspection PyTypeChecker -ISetupConfiguration._methods_ = [ - # Enumerates all completed product instances installed. - COMMETHOD( - [], - HRESULT, - "EnumInstances", - (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") - ), - # Gets the instance for the current process path. - COMMETHOD( - [], - HRESULT, - "GetInstanceForCurrentProcess", - (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") - ), - # Gets the instance for the given path. - COMMETHOD( - [], - HRESULT, - "GetInstanceForPath", - (['in'], LPCWSTR, "wzPath"), - (['out'], POINTER(POINTER(ISetupInstance)), "ppInstance") - ) -] - -# noinspection PyTypeChecker -ISetupConfiguration2._methods_ = [ - # Enumerates all product instances. - COMMETHOD( - [], - HRESULT, - "EnumAllInstances", - (['out'], POINTER(POINTER(IEnumSetupInstances)), "ppEnumInstances") - ) -] - -ISetupHelper._methods_ = [ - # Parses a dotted quad version string into a 64-bit unsigned integer. - COMMETHOD( - [], - HRESULT, - "ParseVersion", - (['in'], LPCOLESTR, "pwszVersion"), - (['out'], PULONGLONG, "pullVersion") - ), - # Parses a dotted quad version string into a 64-bit unsigned integer. - COMMETHOD( - [], - HRESULT, - "ParseVersionRange", - (['in'], LPCOLESTR, "pwszVersionRange"), - (['out'], PULONGLONG, "pullMinVersion"), - (['out'], PULONGLONG, "pullMaxVersion") - ) -] - -ISetupErrorState._methods_ = ( - # Gets an array of failed package references. - COMMETHOD( - [], - HRESULT, - "GetFailedPackages", - (['out'], POINTER(LPSAFEARRAY), "ppsaFailedPackages") - ), - # Gets an array of skipped package references. - COMMETHOD( - [], - HRESULT, - "GetSkippedPackages", - (['out'], POINTER(LPSAFEARRAY), "ppsaSkippedPackages") - ) -) - -ISetupErrorState2._methods_ = ( - # Gets the path to the error log. - COMMETHOD( - [], - HRESULT, - "GetErrorLogFilePath", - (['out'], POINTER(BSTR), "pbstrErrorLogFilePath") - ), - # Gets the path to the main setup log. - COMMETHOD( - [], - HRESULT, - "GetLogFilePath", - (['out'], POINTER(BSTR), "pbstrLogFilePath") - ) -) - -ISetupFailedPackageReference._methods_ = () - -ISetupFailedPackageReference2._methods_ = ( - # Gets the path to the optional package log. - COMMETHOD( - [], - HRESULT, - "GetLogFilePath", - (['out'], POINTER(BSTR), "pbstrLogFilePath") - ), - # Gets the description of the package failure. - COMMETHOD( - [], - HRESULT, - "GetDescription", - (['out'], POINTER(BSTR), "pbstrDescription") - ), - # Gets the signature to use for feedback reporting. - COMMETHOD( - [], - HRESULT, - "GetSignature", - (['out'], POINTER(BSTR), "pbstrSignature") - ), - # Gets the array of details for this package failure. - COMMETHOD( - [], - HRESULT, - "GetDetails", - (['out'], POINTER(LPSAFEARRAY), "ppsaDetails") - ), - # Gets an array of packages affected by this package failure. - COMMETHOD( - [], - HRESULT, - "GetAffectedPackages", - (['out'], POINTER(LPSAFEARRAY), "ppsaAffectedPackages") - ) -) - -ISetupPropertyStore._methods_ = ( - # Gets an array of property names in this property store. - COMMETHOD( - [], - HRESULT, - "GetNames", - (['out'], POINTER(LPSAFEARRAY), "ppsaNames") - ), - # Gets the value of a named property in this property store. - COMMETHOD( - [], - HRESULT, - "GetValue", - (['in'], LPCOLESTR, "pwszName"), - (['in'], LPVARIANT, "pvtValue") - ) -) - -ISetupLocalizedPropertyStore._methods_ = ( - # Gets an array of property names in this property store. - COMMETHOD( - [], - HRESULT, - "GetNames", - (['in'], LCID, "lcid"), - (['out'], POINTER(LPSAFEARRAY), "ppsaNames") - ), - # Gets the value of a named property in this property store. - COMMETHOD( - [], - HRESULT, - "GetValue", - (['in'], LPCOLESTR, "pwszName"), - (['in'], LCID, "lcid"), - (['out'], LPVARIANT, "pvtValue") - ) -) - -CLSID_SetupConfiguration = CLSID("{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}") - - -# This class implements ISetupConfiguration, ISetupConfiguration2 and -# ISetupHelper. -class SetupConfiguration(IUnknown): - _instance_ = None - _iid_ = CLSID_SetupConfiguration - - ISetupConfiguration = ISetupConfiguration - ISetupConfiguration2 = ISetupConfiguration2 - ISetupHelper = ISetupHelper - - # Gets an ISetupConfiguration that provides information about - # product instances installed on the machine. - # noinspection PyTypeChecker - _methods_ = [ - COMMETHOD( - [], - HRESULT, - "GetSetupConfiguration", - ( - ['out'], - POINTER(POINTER(ISetupConfiguration)), - "ppConfiguration" - ), - ([], LPVOID, "pReserved") - ) - ] - - @classmethod - def _callback(cls, _): - cls._instance_ = None - CoUninitialize() - - @classmethod - def GetSetupConfiguration(cls): - if cls._instance_ is None: - CoInitialize() - # noinspection PyCallingNonCallable - instance = CoCreateInstance( - CLSID_SetupConfiguration, - ISetupConfiguration, - CLSCTX_ALL - )() - - cls._instance_ = weakref.ref(instance, cls._callback) - else: - instance = cls._instance_() - - return instance - - -atexit.register(_shutdown) - - -if __name__ == '__main__': - setup_config = SetupConfiguration.GetSetupConfiguration() - print(setup_config) diff --git a/pyproject.toml b/pyproject.toml index edebeef8d6..8aebd7fdd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,7 @@ requires=[ "setuptools>=57.5.0", "wheel", + "pyMSVC;sys_platform=='win32'", "appdirs", "sip==5.5.0", "twine", diff --git a/requirements/devel.txt b/requirements/devel.txt index 18164bdcc9..5c26d7b808 100644 --- a/requirements/devel.txt +++ b/requirements/devel.txt @@ -3,6 +3,7 @@ appdirs setuptools < 45 ; python_version < '3.0' setuptools ; python_version >= '3.0' +pyMSVC ; sys_platform == 'win32' sip == 5.5.0 wheel