Path: blob/main/Tools/c-analyzer/distutils/msvc9compiler.py
12 views
"""distutils.msvc9compiler12Contains MSVCCompiler, an implementation of the abstract CCompiler class3for the Microsoft Visual Studio 2008.45The module is compatible with VS 2005 and VS 2008. You can find legacy support6for older versions of VS in distutils.msvccompiler.7"""89# Written by Perry Stoll10# hacked by Robin Becker and Thomas Heller to do a better job of11# finding DevStudio (through the registry)12# ported to VS2005 and VS 2008 by Christian Heimes1314import os15import subprocess16import sys17import re1819from distutils.errors import DistutilsPlatformError20from distutils.ccompiler import CCompiler21from distutils import log2223import winreg2425RegOpenKeyEx = winreg.OpenKeyEx26RegEnumKey = winreg.EnumKey27RegEnumValue = winreg.EnumValue28RegError = winreg.error2930HKEYS = (winreg.HKEY_USERS,31winreg.HKEY_CURRENT_USER,32winreg.HKEY_LOCAL_MACHINE,33winreg.HKEY_CLASSES_ROOT)3435NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32)36if NATIVE_WIN64:37# Visual C++ is a 32-bit application, so we need to look in38# the corresponding registry branch, if we're running a39# 64-bit Python on Win6440VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f"41WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows"42NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework"43else:44VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f"45WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows"46NET_BASE = r"Software\Microsoft\.NETFramework"4748# A map keyed by get_platform() return values to values accepted by49# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is50# the param to cross-compile on x86 targeting amd64.)51PLAT_TO_VCVARS = {52'win32' : 'x86',53'win-amd64' : 'amd64',54}5556class Reg:57"""Helper class to read values from the registry58"""5960def get_value(cls, path, key):61for base in HKEYS:62d = cls.read_values(base, path)63if d and key in d:64return d[key]65raise KeyError(key)66get_value = classmethod(get_value)6768def read_keys(cls, base, key):69"""Return list of registry keys."""70try:71handle = RegOpenKeyEx(base, key)72except RegError:73return None74L = []75i = 076while True:77try:78k = RegEnumKey(handle, i)79except RegError:80break81L.append(k)82i += 183return L84read_keys = classmethod(read_keys)8586def read_values(cls, base, key):87"""Return dict of registry keys and values.8889All names are converted to lowercase.90"""91try:92handle = RegOpenKeyEx(base, key)93except RegError:94return None95d = {}96i = 097while True:98try:99name, value, type = RegEnumValue(handle, i)100except RegError:101break102name = name.lower()103d[cls.convert_mbcs(name)] = cls.convert_mbcs(value)104i += 1105return d106read_values = classmethod(read_values)107108def convert_mbcs(s):109dec = getattr(s, "decode", None)110if dec is not None:111try:112s = dec("mbcs")113except UnicodeError:114pass115return s116convert_mbcs = staticmethod(convert_mbcs)117118class MacroExpander:119120def __init__(self, version):121self.macros = {}122self.vsbase = VS_BASE % version123self.load_macros(version)124125def set_macro(self, macro, path, key):126self.macros["$(%s)" % macro] = Reg.get_value(path, key)127128def load_macros(self, version):129self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir")130self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir")131self.set_macro("FrameworkDir", NET_BASE, "installroot")132try:133if version >= 8.0:134self.set_macro("FrameworkSDKDir", NET_BASE,135"sdkinstallrootv2.0")136else:137raise KeyError("sdkinstallrootv2.0")138except KeyError:139raise DistutilsPlatformError(140"""Python was built with Visual Studio 2008;141extensions must be built with a compiler than can generate compatible binaries.142Visual Studio 2008 was not found on this system. If you have Cygwin installed,143you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")144145if version >= 9.0:146self.set_macro("FrameworkVersion", self.vsbase, "clr version")147self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder")148else:149p = r"Software\Microsoft\NET Framework Setup\Product"150for base in HKEYS:151try:152h = RegOpenKeyEx(base, p)153except RegError:154continue155key = RegEnumKey(h, 0)156d = Reg.get_value(base, r"%s\%s" % (p, key))157self.macros["$(FrameworkVersion)"] = d["version"]158159def sub(self, s):160for k, v in self.macros.items():161s = s.replace(k, v)162return s163164def get_build_version():165"""Return the version of MSVC that was used to build Python.166167For Python 2.3 and up, the version number is included in168sys.version. For earlier versions, assume the compiler is MSVC 6.169"""170prefix = "MSC v."171i = sys.version.find(prefix)172if i == -1:173return 6174i = i + len(prefix)175s, rest = sys.version[i:].split(" ", 1)176majorVersion = int(s[:-2]) - 6177if majorVersion >= 13:178# v13 was skipped and should be v14179majorVersion += 1180minorVersion = int(s[2:3]) / 10.0181# I don't think paths are affected by minor version in version 6182if majorVersion == 6:183minorVersion = 0184if majorVersion >= 6:185return majorVersion + minorVersion186# else we don't know what version of the compiler this is187return None188189def normalize_and_reduce_paths(paths):190"""Return a list of normalized paths with duplicates removed.191192The current order of paths is maintained.193"""194# Paths are normalized so things like: /a and /a/ aren't both preserved.195reduced_paths = []196for p in paths:197np = os.path.normpath(p)198# XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.199if np not in reduced_paths:200reduced_paths.append(np)201return reduced_paths202203def removeDuplicates(variable):204"""Remove duplicate values of an environment variable.205"""206oldList = variable.split(os.pathsep)207newList = []208for i in oldList:209if i not in newList:210newList.append(i)211newVariable = os.pathsep.join(newList)212return newVariable213214def find_vcvarsall(version):215"""Find the vcvarsall.bat file216217At first it tries to find the productdir of VS 2008 in the registry. If218that fails it falls back to the VS90COMNTOOLS env var.219"""220vsbase = VS_BASE % version221try:222productdir = Reg.get_value(r"%s\Setup\VC" % vsbase,223"productdir")224except KeyError:225log.debug("Unable to find productdir in registry")226productdir = None227228if not productdir or not os.path.isdir(productdir):229toolskey = "VS%0.f0COMNTOOLS" % version230toolsdir = os.environ.get(toolskey, None)231232if toolsdir and os.path.isdir(toolsdir):233productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC")234productdir = os.path.abspath(productdir)235if not os.path.isdir(productdir):236log.debug("%s is not a valid directory" % productdir)237return None238else:239log.debug("Env var %s is not set or invalid" % toolskey)240if not productdir:241log.debug("No productdir found")242return None243vcvarsall = os.path.join(productdir, "vcvarsall.bat")244if os.path.isfile(vcvarsall):245return vcvarsall246log.debug("Unable to find vcvarsall.bat")247return None248249def query_vcvarsall(version, arch="x86"):250"""Launch vcvarsall.bat and read the settings from its environment251"""252vcvarsall = find_vcvarsall(version)253interesting = {"include", "lib", "libpath", "path"}254result = {}255256if vcvarsall is None:257raise DistutilsPlatformError("Unable to find vcvarsall.bat")258log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version)259popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch),260stdout=subprocess.PIPE,261stderr=subprocess.PIPE)262try:263stdout, stderr = popen.communicate()264if popen.wait() != 0:265raise DistutilsPlatformError(stderr.decode("mbcs"))266267stdout = stdout.decode("mbcs")268for line in stdout.split("\n"):269line = Reg.convert_mbcs(line)270if '=' not in line:271continue272line = line.strip()273key, value = line.split('=', 1)274key = key.lower()275if key in interesting:276if value.endswith(os.pathsep):277value = value[:-1]278result[key] = removeDuplicates(value)279280finally:281popen.stdout.close()282popen.stderr.close()283284if len(result) != len(interesting):285raise ValueError(str(list(result.keys())))286287return result288289# More globals290VERSION = get_build_version()291if VERSION < 8.0:292raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)293# MACROS = MacroExpander(VERSION)294295class MSVCCompiler(CCompiler) :296"""Concrete class that implements an interface to Microsoft Visual C++,297as defined by the CCompiler abstract class."""298299compiler_type = 'msvc'300301# Just set this so CCompiler's constructor doesn't barf. We currently302# don't use the 'set_executables()' bureaucracy provided by CCompiler,303# as it really isn't necessary for this sort of single-compiler class.304# Would be nice to have a consistent interface with UnixCCompiler,305# though, so it's worth thinking about.306executables = {}307308# Private class data (need to distinguish C from C++ source for compiler)309_c_extensions = ['.c']310_cpp_extensions = ['.cc', '.cpp', '.cxx']311_rc_extensions = ['.rc']312_mc_extensions = ['.mc']313314# Needed for the filename generation methods provided by the315# base class, CCompiler.316src_extensions = (_c_extensions + _cpp_extensions +317_rc_extensions + _mc_extensions)318res_extension = '.res'319obj_extension = '.obj'320static_lib_extension = '.lib'321shared_lib_extension = '.dll'322static_lib_format = shared_lib_format = '%s%s'323exe_extension = '.exe'324325def __init__(self, verbose=0, dry_run=0, force=0):326CCompiler.__init__ (self, verbose, dry_run, force)327self.__version = VERSION328self.__root = r"Software\Microsoft\VisualStudio"329# self.__macros = MACROS330self.__paths = []331# target platform (.plat_name is consistent with 'bdist')332self.plat_name = None333self.__arch = None # deprecated name334self.initialized = False335336# -- Worker methods ------------------------------------------------337338def manifest_setup_ldargs(self, output_filename, build_temp, ld_args):339# If we need a manifest at all, an embedded manifest is recommended.340# See MSDN article titled341# "How to: Embed a Manifest Inside a C/C++ Application"342# (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)343# Ask the linker to generate the manifest in the temp dir, so344# we can check it, and possibly embed it, later.345temp_manifest = os.path.join(346build_temp,347os.path.basename(output_filename) + ".manifest")348ld_args.append('/MANIFESTFILE:' + temp_manifest)349350def manifest_get_embed_info(self, target_desc, ld_args):351# If a manifest should be embedded, return a tuple of352# (manifest_filename, resource_id). Returns None if no manifest353# should be embedded. See http://bugs.python.org/issue7833 for why354# we want to avoid any manifest for extension modules if we can.355for arg in ld_args:356if arg.startswith("/MANIFESTFILE:"):357temp_manifest = arg.split(":", 1)[1]358break359else:360# no /MANIFESTFILE so nothing to do.361return None362if target_desc == CCompiler.EXECUTABLE:363# by default, executables always get the manifest with the364# CRT referenced.365mfid = 1366else:367# Extension modules try and avoid any manifest if possible.368mfid = 2369temp_manifest = self._remove_visual_c_ref(temp_manifest)370if temp_manifest is None:371return None372return temp_manifest, mfid373374def _remove_visual_c_ref(self, manifest_file):375try:376# Remove references to the Visual C runtime, so they will377# fall through to the Visual C dependency of Python.exe.378# This way, when installed for a restricted user (e.g.379# runtimes are not in WinSxS folder, but in Python's own380# folder), the runtimes do not need to be in every folder381# with .pyd's.382# Returns either the filename of the modified manifest or383# None if no manifest should be embedded.384manifest_f = open(manifest_file)385try:386manifest_buf = manifest_f.read()387finally:388manifest_f.close()389pattern = re.compile(390r"""<assemblyIdentity.*?name=("|')Microsoft\."""\391r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",392re.DOTALL)393manifest_buf = re.sub(pattern, "", manifest_buf)394pattern = r"<dependentAssembly>\s*</dependentAssembly>"395manifest_buf = re.sub(pattern, "", manifest_buf)396# Now see if any other assemblies are referenced - if not, we397# don't want a manifest embedded.398pattern = re.compile(399r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""400r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)401if re.search(pattern, manifest_buf) is None:402return None403404manifest_f = open(manifest_file, 'w')405try:406manifest_f.write(manifest_buf)407return manifest_file408finally:409manifest_f.close()410except OSError:411pass412413# -- Miscellaneous methods -----------------------------------------414415# Helper methods for using the MSVC registry settings416417def find_exe(self, exe):418"""Return path to an MSVC executable program.419420Tries to find the program in several places: first, one of the421MSVC program search paths from the registry; next, the directories422in the PATH environment variable. If any of those work, return an423absolute path that is known to exist. If none of them work, just424return the original program name, 'exe'.425"""426for p in self.__paths:427fn = os.path.join(os.path.abspath(p), exe)428if os.path.isfile(fn):429return fn430431# didn't find it; try existing path432for p in os.environ['Path'].split(';'):433fn = os.path.join(os.path.abspath(p),exe)434if os.path.isfile(fn):435return fn436437return exe438439440