Path: blob/main/test/lib/python3.9/site-packages/setuptools/_distutils/_msvccompiler.py
4799 views
"""distutils._msvccompiler12Contains MSVCCompiler, an implementation of the abstract CCompiler class3for Microsoft Visual Studio 2015.45The module is compatible with VS 2015 and later. You can find legacy support6for older versions in distutils.msvc9compiler and 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 VS 2005 and VS 2008 by Christian Heimes13# ported to VS 2015 by Steve Dower1415import os16import subprocess17import contextlib18import warnings19import unittest.mock20with contextlib.suppress(ImportError):21import winreg2223from distutils.errors import DistutilsExecError, DistutilsPlatformError, \24CompileError, LibError, LinkError25from distutils.ccompiler import CCompiler, gen_lib_options26from distutils import log27from distutils.util import get_platform2829from itertools import count3031def _find_vc2015():32try:33key = winreg.OpenKeyEx(34winreg.HKEY_LOCAL_MACHINE,35r"Software\Microsoft\VisualStudio\SxS\VC7",36access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY37)38except OSError:39log.debug("Visual C++ is not registered")40return None, None4142best_version = 043best_dir = None44with key:45for i in count():46try:47v, vc_dir, vt = winreg.EnumValue(key, i)48except OSError:49break50if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):51try:52version = int(float(v))53except (ValueError, TypeError):54continue55if version >= 14 and version > best_version:56best_version, best_dir = version, vc_dir57return best_version, best_dir5859def _find_vc2017():60"""Returns "15, path" based on the result of invoking vswhere.exe61If no install is found, returns "None, None"6263The version is returned to avoid unnecessarily changing the function64result. It may be ignored when the path is not None.6566If vswhere.exe is not available, by definition, VS 2017 is not67installed.68"""69root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")70if not root:71return None, None7273try:74path = subprocess.check_output([75os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),76"-latest",77"-prerelease",78"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",79"-property", "installationPath",80"-products", "*",81], encoding="mbcs", errors="strict").strip()82except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):83return None, None8485path = os.path.join(path, "VC", "Auxiliary", "Build")86if os.path.isdir(path):87return 15, path8889return None, None9091PLAT_SPEC_TO_RUNTIME = {92'x86' : 'x86',93'x86_amd64' : 'x64',94'x86_arm' : 'arm',95'x86_arm64' : 'arm64'96}9798def _find_vcvarsall(plat_spec):99# bpo-38597: Removed vcruntime return value100_, best_dir = _find_vc2017()101102if not best_dir:103best_version, best_dir = _find_vc2015()104105if not best_dir:106log.debug("No suitable Visual C++ version found")107return None, None108109vcvarsall = os.path.join(best_dir, "vcvarsall.bat")110if not os.path.isfile(vcvarsall):111log.debug("%s cannot be found", vcvarsall)112return None, None113114return vcvarsall, None115116def _get_vc_env(plat_spec):117if os.getenv("DISTUTILS_USE_SDK"):118return {119key.lower(): value120for key, value in os.environ.items()121}122123vcvarsall, _ = _find_vcvarsall(plat_spec)124if not vcvarsall:125raise DistutilsPlatformError("Unable to find vcvarsall.bat")126127try:128out = subprocess.check_output(129'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),130stderr=subprocess.STDOUT,131).decode('utf-16le', errors='replace')132except subprocess.CalledProcessError as exc:133log.error(exc.output)134raise DistutilsPlatformError("Error executing {}"135.format(exc.cmd))136137env = {138key.lower(): value139for key, _, value in140(line.partition('=') for line in out.splitlines())141if key and value142}143144return env145146def _find_exe(exe, paths=None):147"""Return path to an MSVC executable program.148149Tries to find the program in several places: first, one of the150MSVC program search paths from the registry; next, the directories151in the PATH environment variable. If any of those work, return an152absolute path that is known to exist. If none of them work, just153return the original program name, 'exe'.154"""155if not paths:156paths = os.getenv('path').split(os.pathsep)157for p in paths:158fn = os.path.join(os.path.abspath(p), exe)159if os.path.isfile(fn):160return fn161return exe162163# A map keyed by get_platform() return values to values accepted by164# 'vcvarsall.bat'. Always cross-compile from x86 to work with the165# lighter-weight MSVC installs that do not include native 64-bit tools.166PLAT_TO_VCVARS = {167'win32' : 'x86',168'win-amd64' : 'x86_amd64',169'win-arm32' : 'x86_arm',170'win-arm64' : 'x86_arm64'171}172173class MSVCCompiler(CCompiler) :174"""Concrete class that implements an interface to Microsoft Visual C++,175as defined by the CCompiler abstract class."""176177compiler_type = 'msvc'178179# Just set this so CCompiler's constructor doesn't barf. We currently180# don't use the 'set_executables()' bureaucracy provided by CCompiler,181# as it really isn't necessary for this sort of single-compiler class.182# Would be nice to have a consistent interface with UnixCCompiler,183# though, so it's worth thinking about.184executables = {}185186# Private class data (need to distinguish C from C++ source for compiler)187_c_extensions = ['.c']188_cpp_extensions = ['.cc', '.cpp', '.cxx']189_rc_extensions = ['.rc']190_mc_extensions = ['.mc']191192# Needed for the filename generation methods provided by the193# base class, CCompiler.194src_extensions = (_c_extensions + _cpp_extensions +195_rc_extensions + _mc_extensions)196res_extension = '.res'197obj_extension = '.obj'198static_lib_extension = '.lib'199shared_lib_extension = '.dll'200static_lib_format = shared_lib_format = '%s%s'201exe_extension = '.exe'202203204def __init__(self, verbose=0, dry_run=0, force=0):205super().__init__(verbose, dry_run, force)206# target platform (.plat_name is consistent with 'bdist')207self.plat_name = None208self.initialized = False209210def initialize(self, plat_name=None):211# multi-init means we would need to check platform same each time...212assert not self.initialized, "don't init multiple times"213if plat_name is None:214plat_name = get_platform()215# sanity check for platforms to prevent obscure errors later.216if plat_name not in PLAT_TO_VCVARS:217raise DistutilsPlatformError("--plat-name must be one of {}"218.format(tuple(PLAT_TO_VCVARS)))219220# Get the vcvarsall.bat spec for the requested platform.221plat_spec = PLAT_TO_VCVARS[plat_name]222223vc_env = _get_vc_env(plat_spec)224if not vc_env:225raise DistutilsPlatformError("Unable to find a compatible "226"Visual Studio installation.")227228self._paths = vc_env.get('path', '')229paths = self._paths.split(os.pathsep)230self.cc = _find_exe("cl.exe", paths)231self.linker = _find_exe("link.exe", paths)232self.lib = _find_exe("lib.exe", paths)233self.rc = _find_exe("rc.exe", paths) # resource compiler234self.mc = _find_exe("mc.exe", paths) # message compiler235self.mt = _find_exe("mt.exe", paths) # message compiler236237for dir in vc_env.get('include', '').split(os.pathsep):238if dir:239self.add_include_dir(dir.rstrip(os.sep))240241for dir in vc_env.get('lib', '').split(os.pathsep):242if dir:243self.add_library_dir(dir.rstrip(os.sep))244245self.preprocess_options = None246# bpo-38597: Always compile with dynamic linking247# Future releases of Python 3.x will include all past248# versions of vcruntime*.dll for compatibility.249self.compile_options = [250'/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD'251]252253self.compile_options_debug = [254'/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'255]256257ldflags = [258'/nologo', '/INCREMENTAL:NO', '/LTCG'259]260261ldflags_debug = [262'/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'263]264265self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']266self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']267self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']268self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']269self.ldflags_static = [*ldflags]270self.ldflags_static_debug = [*ldflags_debug]271272self._ldflags = {273(CCompiler.EXECUTABLE, None): self.ldflags_exe,274(CCompiler.EXECUTABLE, False): self.ldflags_exe,275(CCompiler.EXECUTABLE, True): self.ldflags_exe_debug,276(CCompiler.SHARED_OBJECT, None): self.ldflags_shared,277(CCompiler.SHARED_OBJECT, False): self.ldflags_shared,278(CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug,279(CCompiler.SHARED_LIBRARY, None): self.ldflags_static,280(CCompiler.SHARED_LIBRARY, False): self.ldflags_static,281(CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug,282}283284self.initialized = True285286# -- Worker methods ------------------------------------------------287288def object_filenames(self,289source_filenames,290strip_dir=0,291output_dir=''):292ext_map = {293**{ext: self.obj_extension for ext in self.src_extensions},294**{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},295}296297output_dir = output_dir or ''298299def make_out_path(p):300base, ext = os.path.splitext(p)301if strip_dir:302base = os.path.basename(base)303else:304_, base = os.path.splitdrive(base)305if base.startswith((os.path.sep, os.path.altsep)):306base = base[1:]307try:308# XXX: This may produce absurdly long paths. We should check309# the length of the result and trim base until we fit within310# 260 characters.311return os.path.join(output_dir, base + ext_map[ext])312except LookupError:313# Better to raise an exception instead of silently continuing314# and later complain about sources and targets having315# different lengths316raise CompileError("Don't know how to compile {}".format(p))317318return list(map(make_out_path, source_filenames))319320321def compile(self, sources,322output_dir=None, macros=None, include_dirs=None, debug=0,323extra_preargs=None, extra_postargs=None, depends=None):324325if not self.initialized:326self.initialize()327compile_info = self._setup_compile(output_dir, macros, include_dirs,328sources, depends, extra_postargs)329macros, objects, extra_postargs, pp_opts, build = compile_info330331compile_opts = extra_preargs or []332compile_opts.append('/c')333if debug:334compile_opts.extend(self.compile_options_debug)335else:336compile_opts.extend(self.compile_options)337338339add_cpp_opts = False340341for obj in objects:342try:343src, ext = build[obj]344except KeyError:345continue346if debug:347# pass the full pathname to MSVC in debug mode,348# this allows the debugger to find the source file349# without asking the user to browse for it350src = os.path.abspath(src)351352if ext in self._c_extensions:353input_opt = "/Tc" + src354elif ext in self._cpp_extensions:355input_opt = "/Tp" + src356add_cpp_opts = True357elif ext in self._rc_extensions:358# compile .RC to .RES file359input_opt = src360output_opt = "/fo" + obj361try:362self.spawn([self.rc] + pp_opts + [output_opt, input_opt])363except DistutilsExecError as msg:364raise CompileError(msg)365continue366elif ext in self._mc_extensions:367# Compile .MC to .RC file to .RES file.368# * '-h dir' specifies the directory for the369# generated include file370# * '-r dir' specifies the target directory of the371# generated RC file and the binary message resource372# it includes373#374# For now (since there are no options to change this),375# we use the source-directory for the include file and376# the build directory for the RC file and message377# resources. This works at least for win32all.378h_dir = os.path.dirname(src)379rc_dir = os.path.dirname(obj)380try:381# first compile .MC to .RC and .H file382self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])383base, _ = os.path.splitext(os.path.basename (src))384rc_file = os.path.join(rc_dir, base + '.rc')385# then compile .RC to .RES file386self.spawn([self.rc, "/fo" + obj, rc_file])387388except DistutilsExecError as msg:389raise CompileError(msg)390continue391else:392# how to handle this file?393raise CompileError("Don't know how to compile {} to {}"394.format(src, obj))395396args = [self.cc] + compile_opts + pp_opts397if add_cpp_opts:398args.append('/EHsc')399args.append(input_opt)400args.append("/Fo" + obj)401args.extend(extra_postargs)402403try:404self.spawn(args)405except DistutilsExecError as msg:406raise CompileError(msg)407408return objects409410411def create_static_lib(self,412objects,413output_libname,414output_dir=None,415debug=0,416target_lang=None):417418if not self.initialized:419self.initialize()420objects, output_dir = self._fix_object_args(objects, output_dir)421output_filename = self.library_filename(output_libname,422output_dir=output_dir)423424if self._need_link(objects, output_filename):425lib_args = objects + ['/OUT:' + output_filename]426if debug:427pass # XXX what goes here?428try:429log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))430self.spawn([self.lib] + lib_args)431except DistutilsExecError as msg:432raise LibError(msg)433else:434log.debug("skipping %s (up-to-date)", output_filename)435436437def link(self,438target_desc,439objects,440output_filename,441output_dir=None,442libraries=None,443library_dirs=None,444runtime_library_dirs=None,445export_symbols=None,446debug=0,447extra_preargs=None,448extra_postargs=None,449build_temp=None,450target_lang=None):451452if not self.initialized:453self.initialize()454objects, output_dir = self._fix_object_args(objects, output_dir)455fixed_args = self._fix_lib_args(libraries, library_dirs,456runtime_library_dirs)457libraries, library_dirs, runtime_library_dirs = fixed_args458459if runtime_library_dirs:460self.warn("I don't know what to do with 'runtime_library_dirs': "461+ str(runtime_library_dirs))462463lib_opts = gen_lib_options(self,464library_dirs, runtime_library_dirs,465libraries)466if output_dir is not None:467output_filename = os.path.join(output_dir, output_filename)468469if self._need_link(objects, output_filename):470ldflags = self._ldflags[target_desc, debug]471472export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]473474ld_args = (ldflags + lib_opts + export_opts +475objects + ['/OUT:' + output_filename])476477# The MSVC linker generates .lib and .exp files, which cannot be478# suppressed by any linker switches. The .lib files may even be479# needed! Make sure they are generated in the temporary build480# directory. Since they have different names for debug and release481# builds, they can go into the same directory.482build_temp = os.path.dirname(objects[0])483if export_symbols is not None:484(dll_name, dll_ext) = os.path.splitext(485os.path.basename(output_filename))486implib_file = os.path.join(487build_temp,488self.library_filename(dll_name))489ld_args.append ('/IMPLIB:' + implib_file)490491if extra_preargs:492ld_args[:0] = extra_preargs493if extra_postargs:494ld_args.extend(extra_postargs)495496output_dir = os.path.dirname(os.path.abspath(output_filename))497self.mkpath(output_dir)498try:499log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))500self.spawn([self.linker] + ld_args)501except DistutilsExecError as msg:502raise LinkError(msg)503else:504log.debug("skipping %s (up-to-date)", output_filename)505506def spawn(self, cmd):507env = dict(os.environ, PATH=self._paths)508with self._fallback_spawn(cmd, env) as fallback:509return super().spawn(cmd, env=env)510return fallback.value511512@contextlib.contextmanager513def _fallback_spawn(self, cmd, env):514"""515Discovered in pypa/distutils#15, some tools monkeypatch the compiler,516so the 'env' kwarg causes a TypeError. Detect this condition and517restore the legacy, unsafe behavior.518"""519bag = type('Bag', (), {})()520try:521yield bag522except TypeError as exc:523if "unexpected keyword argument 'env'" not in str(exc):524raise525else:526return527warnings.warn(528"Fallback spawn triggered. Please update distutils monkeypatch.")529with unittest.mock.patch.dict('os.environ', env):530bag.value = super().spawn(cmd)531532# -- Miscellaneous methods -----------------------------------------533# These are all used by the 'gen_lib_options() function, in534# ccompiler.py.535536def library_dir_option(self, dir):537return "/LIBPATH:" + dir538539def runtime_library_dir_option(self, dir):540raise DistutilsPlatformError(541"don't know how to set runtime library search path for MSVC")542543def library_option(self, lib):544return self.library_filename(lib)545546def find_library_file(self, dirs, lib, debug=0):547# Prefer a debugging library if found (and requested), but deal548# with it if we don't have one.549if debug:550try_names = [lib + "_d", lib]551else:552try_names = [lib]553for dir in dirs:554for name in try_names:555libfile = os.path.join(dir, self.library_filename(name))556if os.path.isfile(libfile):557return libfile558else:559# Oops, didn't find it in *any* of 'dirs'560return None561562563