| """distutils._msvccompiler | |
| Contains MSVCCompiler, an implementation of the abstract CCompiler class | |
| for Microsoft Visual Studio 2015. | |
| The module is compatible with VS 2015 and later. You can find legacy support | |
| for older versions in distutils.msvc9compiler and distutils.msvccompiler. | |
| """ | |
| # Written by Perry Stoll | |
| # hacked by Robin Becker and Thomas Heller to do a better job of | |
| # finding DevStudio (through the registry) | |
| # ported to VS 2005 and VS 2008 by Christian Heimes | |
| # ported to VS 2015 by Steve Dower | |
| import os | |
| import subprocess | |
| import winreg | |
| from distutils.errors import DistutilsPlatformError | |
| from distutils.ccompiler import CCompiler | |
| from distutils import log | |
| from itertools import count | |
| def _find_vc2015(): | |
| try: | |
| key = winreg.OpenKeyEx( | |
| winreg.HKEY_LOCAL_MACHINE, | |
| r"Software\Microsoft\VisualStudio\SxS\VC7", | |
| access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY | |
| ) | |
| except OSError: | |
| log.debug("Visual C++ is not registered") | |
| return None, None | |
| best_version = 0 | |
| best_dir = None | |
| with key: | |
| for i in count(): | |
| try: | |
| v, vc_dir, vt = winreg.EnumValue(key, i) | |
| except OSError: | |
| break | |
| if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): | |
| try: | |
| version = int(float(v)) | |
| except (ValueError, TypeError): | |
| continue | |
| if version >= 14 and version > best_version: | |
| best_version, best_dir = version, vc_dir | |
| return best_version, best_dir | |
| def _find_vc2017(): | |
| """Returns "15, path" based on the result of invoking vswhere.exe | |
| If no install is found, returns "None, None" | |
| The version is returned to avoid unnecessarily changing the function | |
| result. It may be ignored when the path is not None. | |
| If vswhere.exe is not available, by definition, VS 2017 is not | |
| installed. | |
| """ | |
| root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") | |
| if not root: | |
| return None, None | |
| try: | |
| path = subprocess.check_output([ | |
| os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), | |
| "-latest", | |
| "-prerelease", | |
| "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", | |
| "-property", "installationPath", | |
| "-products", "*", | |
| ], encoding="mbcs", errors="strict").strip() | |
| except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): | |
| return None, None | |
| path = os.path.join(path, "VC", "Auxiliary", "Build") | |
| if os.path.isdir(path): | |
| return 15, path | |
| return None, None | |
| PLAT_SPEC_TO_RUNTIME = { | |
| 'x86' : 'x86', | |
| 'x86_amd64' : 'x64', | |
| 'x86_arm' : 'arm', | |
| 'x86_arm64' : 'arm64' | |
| } | |
| def _find_vcvarsall(plat_spec): | |
| # bpo-38597: Removed vcruntime return value | |
| _, best_dir = _find_vc2017() | |
| if not best_dir: | |
| best_version, best_dir = _find_vc2015() | |
| if not best_dir: | |
| log.debug("No suitable Visual C++ version found") | |
| return None, None | |
| vcvarsall = os.path.join(best_dir, "vcvarsall.bat") | |
| if not os.path.isfile(vcvarsall): | |
| log.debug("%s cannot be found", vcvarsall) | |
| return None, None | |
| return vcvarsall, None | |
| def _get_vc_env(plat_spec): | |
| if os.getenv("DISTUTILS_USE_SDK"): | |
| return { | |
| key.lower(): value | |
| for key, value in os.environ.items() | |
| } | |
| vcvarsall, _ = _find_vcvarsall(plat_spec) | |
| if not vcvarsall: | |
| raise DistutilsPlatformError("Unable to find vcvarsall.bat") | |
| try: | |
| out = subprocess.check_output( | |
| 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), | |
| stderr=subprocess.STDOUT, | |
| ).decode('utf-16le', errors='replace') | |
| except subprocess.CalledProcessError as exc: | |
| log.error(exc.output) | |
| raise DistutilsPlatformError("Error executing {}" | |
| .format(exc.cmd)) | |
| env = { | |
| key.lower(): value | |
| for key, _, value in | |
| (line.partition('=') for line in out.splitlines()) | |
| if key and value | |
| } | |
| return env | |
| def _find_exe(exe, paths=None): | |
| """Return path to an MSVC executable program. | |
| Tries to find the program in several places: first, one of the | |
| MSVC program search paths from the registry; next, the directories | |
| in the PATH environment variable. If any of those work, return an | |
| absolute path that is known to exist. If none of them work, just | |
| return the original program name, 'exe'. | |
| """ | |
| if not paths: | |
| paths = os.getenv('path').split(os.pathsep) | |
| for p in paths: | |
| fn = os.path.join(os.path.abspath(p), exe) | |
| if os.path.isfile(fn): | |
| return fn | |
| return exe | |
| # A map keyed by get_platform() return values to values accepted by | |
| # 'vcvarsall.bat'. Always cross-compile from x86 to work with the | |
| # lighter-weight MSVC installs that do not include native 64-bit tools. | |
| PLAT_TO_VCVARS = { | |
| 'win32' : 'x86', | |
| 'win-amd64' : 'x86_amd64', | |
| 'win-arm32' : 'x86_arm', | |
| 'win-arm64' : 'x86_arm64' | |
| } | |
| class MSVCCompiler(CCompiler) : | |
| """Concrete class that implements an interface to Microsoft Visual C++, | |
| as defined by the CCompiler abstract class.""" | |
| compiler_type = 'msvc' | |
| # Just set this so CCompiler's constructor doesn't barf. We currently | |
| # don't use the 'set_executables()' bureaucracy provided by CCompiler, | |
| # as it really isn't necessary for this sort of single-compiler class. | |
| # Would be nice to have a consistent interface with UnixCCompiler, | |
| # though, so it's worth thinking about. | |
| executables = {} | |
| # Private class data (need to distinguish C from C++ source for compiler) | |
| _c_extensions = ['.c'] | |
| _cpp_extensions = ['.cc', '.cpp', '.cxx'] | |
| _rc_extensions = ['.rc'] | |
| _mc_extensions = ['.mc'] | |
| # Needed for the filename generation methods provided by the | |
| # base class, CCompiler. | |
| src_extensions = (_c_extensions + _cpp_extensions + | |
| _rc_extensions + _mc_extensions) | |
| res_extension = '.res' | |
| obj_extension = '.obj' | |
| static_lib_extension = '.lib' | |
| shared_lib_extension = '.dll' | |
| static_lib_format = shared_lib_format = '%s%s' | |
| exe_extension = '.exe' | |
| def __init__(self, verbose=0, dry_run=0, force=0): | |
| CCompiler.__init__ (self, verbose, dry_run, force) | |
| # target platform (.plat_name is consistent with 'bdist') | |
| self.plat_name = None | |
| self.initialized = False |