codekingpro/portable-devtools
114k
1"""distutils.msvccompiler2 3Contains MSVCCompiler, an implementation of the abstract CCompiler class4for the Microsoft Visual Studio.5"""6 7# Written by Perry Stoll8# hacked by Robin Becker and Thomas Heller to do a better job of9# finding DevStudio (through the registry)10 11import os12import sys13import warnings14 15from ._log import log16from .ccompiler import CCompiler, gen_lib_options17from .errors import (18 CompileError,19 DistutilsExecError,20 DistutilsPlatformError,21 LibError,22 LinkError,23)24 25_can_read_reg = False26try:27 import winreg28 29 _can_read_reg = True30 hkey_mod = winreg31 32 RegOpenKeyEx = winreg.OpenKeyEx33 RegEnumKey = winreg.EnumKey34 RegEnumValue = winreg.EnumValue35 RegError = winreg.error36 37except ImportError:38 try:39 import win32api40 import win32con41 42 _can_read_reg = True43 hkey_mod = win32con44 45 RegOpenKeyEx = win32api.RegOpenKeyEx46 RegEnumKey = win32api.RegEnumKey47 RegEnumValue = win32api.RegEnumValue48 RegError = win32api.error49 except ImportError:50 log.info(51 "Warning: Can't read registry to find the "52 "necessary compiler setting\n"53 "Make sure that Python modules winreg, "54 "win32api or win32con are installed."55 )56 pass57 58if _can_read_reg:59 HKEYS = (60 hkey_mod.HKEY_USERS,61 hkey_mod.HKEY_CURRENT_USER,62 hkey_mod.HKEY_LOCAL_MACHINE,63 hkey_mod.HKEY_CLASSES_ROOT,64 )65 66 67warnings.warn(68 "msvccompiler is deprecated and slated to be removed "69 "in the future. Please discontinue use or file an issue "70 "with pypa/distutils describing your use case.",71 DeprecationWarning,72)73 74 75def read_keys(base, key):76 """Return list of registry keys."""77 try:78 handle = RegOpenKeyEx(base, key)79 except RegError:80 return None81 L = []82 i = 083 while True:84 try:85 k = RegEnumKey(handle, i)86 except RegError:87 break88 L.append(k)89 i += 190 return L91 92 93def read_values(base, key):94 """Return dict of registry keys and values.95 96 All names are converted to lowercase.97 """98 try:99 handle = RegOpenKeyEx(base, key)100 except RegError:101 return None102 d = {}103 i = 0104 while True:105 try:106 name, value, type = RegEnumValue(handle, i)107 except RegError:108 break109 name = name.lower()110 d[convert_mbcs(name)] = convert_mbcs(value)111 i += 1112 return d113 114 115def convert_mbcs(s):116 dec = getattr(s, "decode", None)117 if dec is not None:118 try:119 s = dec("mbcs")120 except UnicodeError:121 pass122 return s123 124 125class MacroExpander:126 def __init__(self, version):127 self.macros = {}128 self.load_macros(version)129 130 def set_macro(self, macro, path, key):131 for base in HKEYS:132 d = read_values(base, path)133 if d:134 self.macros["$(%s)" % macro] = d[key]135 break136 137 def load_macros(self, version):138 vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version139 self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir")140 self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir")141 net = r"Software\Microsoft\.NETFramework"142 self.set_macro("FrameworkDir", net, "installroot")143 try:144 if version > 7.0:145 self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")146 else:147 self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")148 except KeyError:149 raise DistutilsPlatformError(150 """Python was built with Visual Studio 2003;151extensions must be built with a compiler than can generate compatible binaries.152Visual Studio 2003 was not found on this system. If you have Cygwin installed,153you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""154 )155 156 p = r"Software\Microsoft\NET Framework Setup\Product"157 for base in HKEYS:158 try:159 h = RegOpenKeyEx(base, p)160 except RegError:161 continue162 key = RegEnumKey(h, 0)163 d = read_values(base, rf"{p}\{key}")164 self.macros["$(FrameworkVersion)"] = d["version"]165 166 def sub(self, s):167 for k, v in self.macros.items():168 s = s.replace(k, v)169 return s170 171 172def get_build_version():173 """Return the version of MSVC that was used to build Python.174 175 For Python 2.3 and up, the version number is included in176 sys.version. For earlier versions, assume the compiler is MSVC 6.177 """178 prefix = "MSC v."179 i = sys.version.find(prefix)180 if i == -1:181 return 6182 i = i + len(prefix)183 s, rest = sys.version[i:].split(" ", 1)184 majorVersion = int(s[:-2]) - 6185 if majorVersion >= 13:186 # v13 was skipped and should be v14187 majorVersion += 1188 minorVersion = int(s[2:3]) / 10.0189 # I don't think paths are affected by minor version in version 6190 if majorVersion == 6:191 minorVersion = 0192 if majorVersion >= 6:193 return majorVersion + minorVersion194 # else we don't know what version of the compiler this is195 return None196 197 198def get_build_architecture():199 """Return the processor architecture.200 201 Possible results are "Intel" or "AMD64".202 """203 204 prefix = " bit ("205 i = sys.version.find(prefix)206 if i == -1:207 return "Intel"208 j = sys.version.find(")", i)209 return sys.version[i + len(prefix) : j]210 211 212def normalize_and_reduce_paths(paths):213 """Return a list of normalized paths with duplicates removed.214 215 The current order of paths is maintained.216 """217 # Paths are normalized so things like: /a and /a/ aren't both preserved.218 reduced_paths = []219 for p in paths:220 np = os.path.normpath(p)221 # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.222 if np not in reduced_paths:223 reduced_paths.append(np)224 return reduced_paths225 226 227class MSVCCompiler(CCompiler):228 """Concrete class that implements an interface to Microsoft Visual C++,229 as defined by the CCompiler abstract class."""230 231 compiler_type = 'msvc'232 233 # Just set this so CCompiler's constructor doesn't barf. We currently234 # don't use the 'set_executables()' bureaucracy provided by CCompiler,235 # as it really isn't necessary for this sort of single-compiler class.236 # Would be nice to have a consistent interface with UnixCCompiler,237 # though, so it's worth thinking about.238 executables = {}239 240 # Private class data (need to distinguish C from C++ source for compiler)241 _c_extensions = ['.c']242 _cpp_extensions = ['.cc', '.cpp', '.cxx']243 _rc_extensions = ['.rc']244 _mc_extensions = ['.mc']245 246 # Needed for the filename generation methods provided by the247 # base class, CCompiler.248 src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions249 res_extension = '.res'250 obj_extension = '.obj'251 static_lib_extension = '.lib'252 shared_lib_extension = '.dll'253 static_lib_format = shared_lib_format = '%s%s'254 exe_extension = '.exe'255 256 def __init__(self, verbose=0, dry_run=0, force=0):257 super().__init__(verbose, dry_run, force)258 self.__version = get_build_version()259 self.__arch = get_build_architecture()260 if self.__arch == "Intel":261 # x86262 if self.__version >= 7:263 self.__root = r"Software\Microsoft\VisualStudio"264 self.__macros = MacroExpander(self.__version)265 else:266 self.__root = r"Software\Microsoft\Devstudio"267 self.__product = "Visual Studio version %s" % self.__version268 else:269 # Win64. Assume this was built with the platform SDK270 self.__product = "Microsoft SDK compiler %s" % (self.__version + 6)271 272 self.initialized = False273 274 def initialize(self):275 self.__paths = []276 if (277 "DISTUTILS_USE_SDK" in os.environ278 and "MSSdk" in os.environ279 and self.find_exe("cl.exe")280 ):281 # Assume that the SDK set up everything alright; don't try to be282 # smarter283 self.cc = "cl.exe"284 self.linker = "link.exe"285 self.lib = "lib.exe"286 self.rc = "rc.exe"287 self.mc = "mc.exe"288 else:289 self.__paths = self.get_msvc_paths("path")290 291 if len(self.__paths) == 0:292 raise DistutilsPlatformError(293 "Python was built with %s, "294 "and extensions need to be built with the same "295 "version of the compiler, but it isn't installed." % self.__product296 )297 298 self.cc = self.find_exe("cl.exe")299 self.linker = self.find_exe("link.exe")300 self.lib = self.find_exe("lib.exe")301 self.rc = self.find_exe("rc.exe") # resource compiler302 self.mc = self.find_exe("mc.exe") # message compiler303 self.set_path_env_var('lib')304 self.set_path_env_var('include')305 306 # extend the MSVC path with the current path307 try:308 for p in os.environ['path'].split(';'):309 self.__paths.append(p)310 except KeyError:311 pass312 self.__paths = normalize_and_reduce_paths(self.__paths)313 os.environ['path'] = ";".join(self.__paths)314 315 self.preprocess_options = None316 if self.__arch == "Intel":317 self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GX', '/DNDEBUG']318 self.compile_options_debug = [319 '/nologo',320 '/Od',321 '/MDd',322 '/W3',323 '/GX',324 '/Z7',325 '/D_DEBUG',326 ]327 else:328 # Win64329 self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']330 self.compile_options_debug = [331 '/nologo',332 '/Od',333 '/MDd',334 '/W3',335 '/GS-',336 '/Z7',337 '/D_DEBUG',338 ]339 340 self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']341 if self.__version >= 7:342 self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']343 else:344 self.ldflags_shared_debug = [345 '/DLL',346 '/nologo',347 '/INCREMENTAL:no',348 '/pdb:None',349 '/DEBUG',350 ]351 self.ldflags_static = ['/nologo']352 353 self.initialized = True354 355 # -- Worker methods ------------------------------------------------356 357 def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):358 # Copied from ccompiler.py, extended to return .res as 'object'-file359 # for .rc input file360 if output_dir is None:361 output_dir = ''362 obj_names = []363 for src_name in source_filenames:364 (base, ext) = os.path.splitext(src_name)365 base = os.path.splitdrive(base)[1] # Chop off the drive366 base = base[os.path.isabs(base) :] # If abs, chop off leading /367 if ext not in self.src_extensions:368 # Better to raise an exception instead of silently continuing369 # and later complain about sources and targets having370 # different lengths371 raise CompileError("Don't know how to compile %s" % src_name)372 if strip_dir:373 base = os.path.basename(base)374 if ext in self._rc_extensions:375 obj_names.append(os.path.join(output_dir, base + self.res_extension))376 elif ext in self._mc_extensions:377 obj_names.append(os.path.join(output_dir, base + self.res_extension))378 else:379 obj_names.append(os.path.join(output_dir, base + self.obj_extension))380 return obj_names381 382 def compile( # noqa: C901383 self,384 sources,385 output_dir=None,386 macros=None,387 include_dirs=None,388 debug=0,389 extra_preargs=None,390 extra_postargs=None,391 depends=None,392 ):393 if not self.initialized:394 self.initialize()395 compile_info = self._setup_compile(396 output_dir, macros, include_dirs, sources, depends, extra_postargs397 )398 macros, objects, extra_postargs, pp_opts, build = compile_info399 400 compile_opts = extra_preargs or []401 compile_opts.append('/c')402 if debug:403 compile_opts.extend(self.compile_options_debug)404 else:405 compile_opts.extend(self.compile_options)406 407 for obj in objects:408 try:409 src, ext = build[obj]410 except KeyError:411 continue412 if debug:413 # pass the full pathname to MSVC in debug mode,414 # this allows the debugger to find the source file415 # without asking the user to browse for it416 src = os.path.abspath(src)417 418 if ext in self._c_extensions:419 input_opt = "/Tc" + src420 elif ext in self._cpp_extensions:421 input_opt = "/Tp" + src422 elif ext in self._rc_extensions:423 # compile .RC to .RES file424 input_opt = src425 output_opt = "/fo" + obj426 try:427 self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])428 except DistutilsExecError as msg:429 raise CompileError(msg)430 continue431 elif ext in self._mc_extensions:432 # Compile .MC to .RC file to .RES file.433 # * '-h dir' specifies the directory for the434 # generated include file435 # * '-r dir' specifies the target directory of the436 # generated RC file and the binary message resource437 # it includes438 #439 # For now (since there are no options to change this),440 # we use the source-directory for the include file and441 # the build directory for the RC file and message442 # resources. This works at least for win32all.443 h_dir = os.path.dirname(src)444 rc_dir = os.path.dirname(obj)445 try:446 # first compile .MC to .RC and .H file447 self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])448 base, _ = os.path.splitext(os.path.basename(src))449 rc_file = os.path.join(rc_dir, base + '.rc')450 # then compile .RC to .RES file451 self.spawn([self.rc] + ["/fo" + obj] + [rc_file])452 453 except DistutilsExecError as msg:454 raise CompileError(msg)455 continue456 else:457 # how to handle this file?458 raise CompileError(f"Don't know how to compile {src} to {obj}")459 460 output_opt = "/Fo" + obj461 try:462 self.spawn(463 [self.cc]464 + compile_opts465 + pp_opts466 + [input_opt, output_opt]467 + extra_postargs468 )469 except DistutilsExecError as msg:470 raise CompileError(msg)471 472 return objects473 474 def create_static_lib(475 self, objects, output_libname, output_dir=None, debug=0, target_lang=None476 ):477 if not self.initialized:478 self.initialize()479 (objects, output_dir) = self._fix_object_args(objects, output_dir)480 output_filename = self.library_filename(output_libname, output_dir=output_dir)481 482 if self._need_link(objects, output_filename):483 lib_args = objects + ['/OUT:' + output_filename]484 if debug:485 pass # XXX what goes here?486 try:487 self.spawn([self.lib] + lib_args)488 except DistutilsExecError as msg:489 raise LibError(msg)490 else:491 log.debug("skipping %s (up-to-date)", output_filename)492 493 def link( # noqa: C901494 self,495 target_desc,496 objects,497 output_filename,498 output_dir=None,499 libraries=None,500 library_dirs=None,501 runtime_library_dirs=None,502 export_symbols=None,503 debug=0,504 extra_preargs=None,505 extra_postargs=None,506 build_temp=None,507 target_lang=None,508 ):509 if not self.initialized:510 self.initialize()511 (objects, output_dir) = self._fix_object_args(objects, output_dir)512 fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)513 (libraries, library_dirs, runtime_library_dirs) = fixed_args514 515 if runtime_library_dirs:516 self.warn(517 "I don't know what to do with 'runtime_library_dirs': "518 + str(runtime_library_dirs)519 )520 521 lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)522 if output_dir is not None:523 output_filename = os.path.join(output_dir, output_filename)524 525 if self._need_link(objects, output_filename):526 if target_desc == CCompiler.EXECUTABLE:527 if debug:528 ldflags = self.ldflags_shared_debug[1:]529 else:530 ldflags = self.ldflags_shared[1:]531 else:532 if debug:533 ldflags = self.ldflags_shared_debug534 else:535 ldflags = self.ldflags_shared536 537 export_opts = []538 for sym in export_symbols or []:539 export_opts.append("/EXPORT:" + sym)540 541 ld_args = (542 ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]543 )544 545 # The MSVC linker generates .lib and .exp files, which cannot be546 # suppressed by any linker switches. The .lib files may even be547 # needed! Make sure they are generated in the temporary build548 # directory. Since they have different names for debug and release549 # builds, they can go into the same directory.550 if export_symbols is not None:551 (dll_name, dll_ext) = os.path.splitext(552 os.path.basename(output_filename)553 )554 implib_file = os.path.join(555 os.path.dirname(objects[0]), self.library_filename(dll_name)556 )557 ld_args.append('/IMPLIB:' + implib_file)558 559 if extra_preargs:560 ld_args[:0] = extra_preargs561 if extra_postargs:562 ld_args.extend(extra_postargs)563 564 self.mkpath(os.path.dirname(output_filename))565 try:566 self.spawn([self.linker] + ld_args)567 except DistutilsExecError as msg:568 raise LinkError(msg)569 570 else:571 log.debug("skipping %s (up-to-date)", output_filename)572 573 # -- Miscellaneous methods -----------------------------------------574 # These are all used by the 'gen_lib_options() function, in575 # ccompiler.py.576 577 def library_dir_option(self, dir):578 return "/LIBPATH:" + dir579 580 def runtime_library_dir_option(self, dir):581 raise DistutilsPlatformError(582 "don't know how to set runtime library search path for MSVC++"583 )584 585 def library_option(self, lib):586 return self.library_filename(lib)587 588 def find_library_file(self, dirs, lib, debug=0):589 # Prefer a debugging library if found (and requested), but deal590 # with it if we don't have one.591 if debug:592 try_names = [lib + "_d", lib]593 else:594 try_names = [lib]595 for dir in dirs:596 for name in try_names:597 libfile = os.path.join(dir, self.library_filename(name))598 if os.path.exists(libfile):599 return libfile600 else:601 # Oops, didn't find it in *any* of 'dirs'602 return None603 604 # Helper methods for using the MSVC registry settings605 606 def find_exe(self, exe):607 """Return path to an MSVC executable program.608 609 Tries to find the program in several places: first, one of the610 MSVC program search paths from the registry; next, the directories611 in the PATH environment variable. If any of those work, return an612 absolute path that is known to exist. If none of them work, just613 return the original program name, 'exe'.614 """615 for p in self.__paths:616 fn = os.path.join(os.path.abspath(p), exe)617 if os.path.isfile(fn):618 return fn619 620 # didn't find it; try existing path621 for p in os.environ['Path'].split(';'):622 fn = os.path.join(os.path.abspath(p), exe)623 if os.path.isfile(fn):624 return fn625 626 return exe627 628 def get_msvc_paths(self, path, platform='x86'):629 """Get a list of devstudio directories (include, lib or path).630 631 Return a list of strings. The list will be empty if unable to632 access the registry or appropriate registry keys not found.633 """634 if not _can_read_reg:635 return []636 637 path = path + " dirs"638 if self.__version >= 7:639 key = rf"{self.__root}\{self.__version:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"640 else:641 key = (642 rf"{self.__root}\6.0\Build System\Components\Platforms"643 rf"\Win32 ({platform})\Directories"644 )645 646 for base in HKEYS:647 d = read_values(base, key)648 if d:649 if self.__version >= 7:650 return self.__macros.sub(d[path]).split(";")651 else:652 return d[path].split(";")653 # MSVC 6 seems to create the registry entries we need only when654 # the GUI is run.655 if self.__version == 6:656 for base in HKEYS:657 if read_values(base, r"%s\6.0" % self.__root) is not None:658 self.warn(659 "It seems you have Visual Studio 6 installed, "660 "but the expected registry settings are not present.\n"661 "You must at least run the Visual Studio GUI once "662 "so that these entries are created."663 )664 break665 return []666 667 def set_path_env_var(self, name):668 """Set environment variable 'name' to an MSVC path type value.669 670 This is equivalent to a SET command prior to execution of spawned671 commands.672 """673 674 if name == "lib":675 p = self.get_msvc_paths("library")676 else:677 p = self.get_msvc_paths(name)678 if p:679 os.environ[name] = ';'.join(p)680 681 682if get_build_version() >= 8.0:683 log.debug("Importing new compiler from distutils.msvc9compiler")684 OldMSVCCompiler = MSVCCompiler685 # get_build_architecture not really relevant now we support cross-compile686 from distutils.msvc9compiler import (687 MacroExpander, # noqa: F811688 MSVCCompiler,689 )690 