Home | History | Annotate | Download | only in distutils
      1 """distutils._msvccompiler
      2 
      3 Contains MSVCCompiler, an implementation of the abstract CCompiler class
      4 for Microsoft Visual Studio 2015.
      5 
      6 The module is compatible with VS 2015 and later. You can find legacy support
      7 for older versions in distutils.msvc9compiler and distutils.msvccompiler.
      8 """
      9 
     10 # Written by Perry Stoll
     11 # hacked by Robin Becker and Thomas Heller to do a better job of
     12 #   finding DevStudio (through the registry)
     13 # ported to VS 2005 and VS 2008 by Christian Heimes
     14 # ported to VS 2015 by Steve Dower
     15 
     16 import os
     17 import shutil
     18 import stat
     19 import subprocess
     20 
     21 from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
     22                              CompileError, LibError, LinkError
     23 from distutils.ccompiler import CCompiler, gen_lib_options
     24 from distutils import log
     25 from distutils.util import get_platform
     26 
     27 import winreg
     28 from itertools import count
     29 
     30 def _find_vcvarsall(plat_spec):
     31     try:
     32         key = winreg.OpenKeyEx(
     33             winreg.HKEY_LOCAL_MACHINE,
     34             r"Software\Microsoft\VisualStudio\SxS\VC7",
     35             access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
     36         )
     37     except OSError:
     38         log.debug("Visual C++ is not registered")
     39         return None, None
     40 
     41     with key:
     42         best_version = 0
     43         best_dir = None
     44         for i in count():
     45             try:
     46                 v, vc_dir, vt = winreg.EnumValue(key, i)
     47             except OSError:
     48                 break
     49             if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
     50                 try:
     51                     version = int(float(v))
     52                 except (ValueError, TypeError):
     53                     continue
     54                 if version >= 14 and version > best_version:
     55                     best_version, best_dir = version, vc_dir
     56         if not best_version:
     57             log.debug("No suitable Visual C++ version found")
     58             return None, None
     59 
     60         vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
     61         if not os.path.isfile(vcvarsall):
     62             log.debug("%s cannot be found", vcvarsall)
     63             return None, None
     64 
     65         vcruntime = None
     66         vcruntime_spec = _VCVARS_PLAT_TO_VCRUNTIME_REDIST.get(plat_spec)
     67         if vcruntime_spec:
     68             vcruntime = os.path.join(best_dir,
     69                 vcruntime_spec.format(best_version))
     70             if not os.path.isfile(vcruntime):
     71                 log.debug("%s cannot be found", vcruntime)
     72                 vcruntime = None
     73 
     74         return vcvarsall, vcruntime
     75 
     76 def _get_vc_env(plat_spec):
     77     if os.getenv("DISTUTILS_USE_SDK"):
     78         return {
     79             key.lower(): value
     80             for key, value in os.environ.items()
     81         }
     82 
     83     vcvarsall, vcruntime = _find_vcvarsall(plat_spec)
     84     if not vcvarsall:
     85         raise DistutilsPlatformError("Unable to find vcvarsall.bat")
     86 
     87     try:
     88         out = subprocess.check_output(
     89             'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
     90             stderr=subprocess.STDOUT,
     91         ).decode('utf-16le', errors='replace')
     92     except subprocess.CalledProcessError as exc:
     93         log.error(exc.output)
     94         raise DistutilsPlatformError("Error executing {}"
     95                 .format(exc.cmd))
     96 
     97     env = {
     98         key.lower(): value
     99         for key, _, value in
    100         (line.partition('=') for line in out.splitlines())
    101         if key and value
    102     }
    103 
    104     if vcruntime:
    105         env['py_vcruntime_redist'] = vcruntime
    106     return env
    107 
    108 def _find_exe(exe, paths=None):
    109     """Return path to an MSVC executable program.
    110 
    111     Tries to find the program in several places: first, one of the
    112     MSVC program search paths from the registry; next, the directories
    113     in the PATH environment variable.  If any of those work, return an
    114     absolute path that is known to exist.  If none of them work, just
    115     return the original program name, 'exe'.
    116     """
    117     if not paths:
    118         paths = os.getenv('path').split(os.pathsep)
    119     for p in paths:
    120         fn = os.path.join(os.path.abspath(p), exe)
    121         if os.path.isfile(fn):
    122             return fn
    123     return exe
    124 
    125 # A map keyed by get_platform() return values to values accepted by
    126 # 'vcvarsall.bat'. Always cross-compile from x86 to work with the
    127 # lighter-weight MSVC installs that do not include native 64-bit tools.
    128 PLAT_TO_VCVARS = {
    129     'win32' : 'x86',
    130     'win-amd64' : 'x86_amd64',
    131 }
    132 
    133 # A map keyed by get_platform() return values to the file under
    134 # the VC install directory containing the vcruntime redistributable.
    135 _VCVARS_PLAT_TO_VCRUNTIME_REDIST = {
    136     'x86' : 'redist\\x86\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
    137     'amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
    138     'x86_amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll',
    139 }
    140 
    141 # A set containing the DLLs that are guaranteed to be available for
    142 # all micro versions of this Python version. Known extension
    143 # dependencies that are not in this set will be copied to the output
    144 # path.
    145 _BUNDLED_DLLS = frozenset(['vcruntime140.dll'])
    146 
    147 class MSVCCompiler(CCompiler) :
    148     """Concrete class that implements an interface to Microsoft Visual C++,
    149        as defined by the CCompiler abstract class."""
    150 
    151     compiler_type = 'msvc'
    152 
    153     # Just set this so CCompiler's constructor doesn't barf.  We currently
    154     # don't use the 'set_executables()' bureaucracy provided by CCompiler,
    155     # as it really isn't necessary for this sort of single-compiler class.
    156     # Would be nice to have a consistent interface with UnixCCompiler,
    157     # though, so it's worth thinking about.
    158     executables = {}
    159 
    160     # Private class data (need to distinguish C from C++ source for compiler)
    161     _c_extensions = ['.c']
    162     _cpp_extensions = ['.cc', '.cpp', '.cxx']
    163     _rc_extensions = ['.rc']
    164     _mc_extensions = ['.mc']
    165 
    166     # Needed for the filename generation methods provided by the
    167     # base class, CCompiler.
    168     src_extensions = (_c_extensions + _cpp_extensions +
    169                       _rc_extensions + _mc_extensions)
    170     res_extension = '.res'
    171     obj_extension = '.obj'
    172     static_lib_extension = '.lib'
    173     shared_lib_extension = '.dll'
    174     static_lib_format = shared_lib_format = '%s%s'
    175     exe_extension = '.exe'
    176 
    177 
    178     def __init__(self, verbose=0, dry_run=0, force=0):
    179         CCompiler.__init__ (self, verbose, dry_run, force)
    180         # target platform (.plat_name is consistent with 'bdist')
    181         self.plat_name = None
    182         self.initialized = False
    183 
    184     def initialize(self, plat_name=None):
    185         # multi-init means we would need to check platform same each time...
    186         assert not self.initialized, "don't init multiple times"
    187         if plat_name is None:
    188             plat_name = get_platform()
    189         # sanity check for platforms to prevent obscure errors later.
    190         if plat_name not in PLAT_TO_VCVARS:
    191             raise DistutilsPlatformError("--plat-name must be one of {}"
    192                                          .format(tuple(PLAT_TO_VCVARS)))
    193 
    194         # Get the vcvarsall.bat spec for the requested platform.
    195         plat_spec = PLAT_TO_VCVARS[plat_name]
    196 
    197         vc_env = _get_vc_env(plat_spec)
    198         if not vc_env:
    199             raise DistutilsPlatformError("Unable to find a compatible "
    200                 "Visual Studio installation.")
    201 
    202         self._paths = vc_env.get('path', '')
    203         paths = self._paths.split(os.pathsep)
    204         self.cc = _find_exe("cl.exe", paths)
    205         self.linker = _find_exe("link.exe", paths)
    206         self.lib = _find_exe("lib.exe", paths)
    207         self.rc = _find_exe("rc.exe", paths)   # resource compiler
    208         self.mc = _find_exe("mc.exe", paths)   # message compiler
    209         self.mt = _find_exe("mt.exe", paths)   # message compiler
    210         self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '')
    211 
    212         for dir in vc_env.get('include', '').split(os.pathsep):
    213             if dir:
    214                 self.add_include_dir(dir)
    215 
    216         for dir in vc_env.get('lib', '').split(os.pathsep):
    217             if dir:
    218                 self.add_library_dir(dir)
    219 
    220         self.preprocess_options = None
    221         # If vcruntime_redist is available, link against it dynamically. Otherwise,
    222         # use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib
    223         # later to dynamically link to ucrtbase but not vcruntime.
    224         self.compile_options = [
    225             '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG'
    226         ]
    227         self.compile_options.append('/MD' if self._vcruntime_redist else '/MT')
    228 
    229         self.compile_options_debug = [
    230             '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
    231         ]
    232 
    233         ldflags = [
    234             '/nologo', '/INCREMENTAL:NO', '/LTCG'
    235         ]
    236         if not self._vcruntime_redist:
    237             ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib'))
    238 
    239         ldflags_debug = [
    240             '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'
    241         ]
    242 
    243         self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']
    244         self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']
    245         self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
    246         self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
    247         self.ldflags_static = [*ldflags]
    248         self.ldflags_static_debug = [*ldflags_debug]
    249 
    250         self._ldflags = {
    251             (CCompiler.EXECUTABLE, None): self.ldflags_exe,
    252             (CCompiler.EXECUTABLE, False): self.ldflags_exe,
    253             (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug,
    254             (CCompiler.SHARED_OBJECT, None): self.ldflags_shared,
    255             (CCompiler.SHARED_OBJECT, False): self.ldflags_shared,
    256             (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug,
    257             (CCompiler.SHARED_LIBRARY, None): self.ldflags_static,
    258             (CCompiler.SHARED_LIBRARY, False): self.ldflags_static,
    259             (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug,
    260         }
    261 
    262         self.initialized = True
    263 
    264     # -- Worker methods ------------------------------------------------
    265 
    266     def object_filenames(self,
    267                          source_filenames,
    268                          strip_dir=0,
    269                          output_dir=''):
    270         ext_map = {
    271             **{ext: self.obj_extension for ext in self.src_extensions},
    272             **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},
    273         }
    274 
    275         output_dir = output_dir or ''
    276 
    277         def make_out_path(p):
    278             base, ext = os.path.splitext(p)
    279             if strip_dir:
    280                 base = os.path.basename(base)
    281             else:
    282                 _, base = os.path.splitdrive(base)
    283                 if base.startswith((os.path.sep, os.path.altsep)):
    284                     base = base[1:]
    285             try:
    286                 # XXX: This may produce absurdly long paths. We should check
    287                 # the length of the result and trim base until we fit within
    288                 # 260 characters.
    289                 return os.path.join(output_dir, base + ext_map[ext])
    290             except LookupError:
    291                 # Better to raise an exception instead of silently continuing
    292                 # and later complain about sources and targets having
    293                 # different lengths
    294                 raise CompileError("Don't know how to compile {}".format(p))
    295 
    296         return list(map(make_out_path, source_filenames))
    297 
    298 
    299     def compile(self, sources,
    300                 output_dir=None, macros=None, include_dirs=None, debug=0,
    301                 extra_preargs=None, extra_postargs=None, depends=None):
    302 
    303         if not self.initialized:
    304             self.initialize()
    305         compile_info = self._setup_compile(output_dir, macros, include_dirs,
    306                                            sources, depends, extra_postargs)
    307         macros, objects, extra_postargs, pp_opts, build = compile_info
    308 
    309         compile_opts = extra_preargs or []
    310         compile_opts.append('/c')
    311         if debug:
    312             compile_opts.extend(self.compile_options_debug)
    313         else:
    314             compile_opts.extend(self.compile_options)
    315 
    316 
    317         add_cpp_opts = False
    318 
    319         for obj in objects:
    320             try:
    321                 src, ext = build[obj]
    322             except KeyError:
    323                 continue
    324             if debug:
    325                 # pass the full pathname to MSVC in debug mode,
    326                 # this allows the debugger to find the source file
    327                 # without asking the user to browse for it
    328                 src = os.path.abspath(src)
    329 
    330             if ext in self._c_extensions:
    331                 input_opt = "/Tc" + src
    332             elif ext in self._cpp_extensions:
    333                 input_opt = "/Tp" + src
    334                 add_cpp_opts = True
    335             elif ext in self._rc_extensions:
    336                 # compile .RC to .RES file
    337                 input_opt = src
    338                 output_opt = "/fo" + obj
    339                 try:
    340                     self.spawn([self.rc] + pp_opts + [output_opt, input_opt])
    341                 except DistutilsExecError as msg:
    342                     raise CompileError(msg)
    343                 continue
    344             elif ext in self._mc_extensions:
    345                 # Compile .MC to .RC file to .RES file.
    346                 #   * '-h dir' specifies the directory for the
    347                 #     generated include file
    348                 #   * '-r dir' specifies the target directory of the
    349                 #     generated RC file and the binary message resource
    350                 #     it includes
    351                 #
    352                 # For now (since there are no options to change this),
    353                 # we use the source-directory for the include file and
    354                 # the build directory for the RC file and message
    355                 # resources. This works at least for win32all.
    356                 h_dir = os.path.dirname(src)
    357                 rc_dir = os.path.dirname(obj)
    358                 try:
    359                     # first compile .MC to .RC and .H file
    360                     self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
    361                     base, _ = os.path.splitext(os.path.basename (src))
    362                     rc_file = os.path.join(rc_dir, base + '.rc')
    363                     # then compile .RC to .RES file
    364                     self.spawn([self.rc, "/fo" + obj, rc_file])
    365 
    366                 except DistutilsExecError as msg:
    367                     raise CompileError(msg)
    368                 continue
    369             else:
    370                 # how to handle this file?
    371                 raise CompileError("Don't know how to compile {} to {}"
    372                                    .format(src, obj))
    373 
    374             args = [self.cc] + compile_opts + pp_opts
    375             if add_cpp_opts:
    376                 args.append('/EHsc')
    377             args.append(input_opt)
    378             args.append("/Fo" + obj)
    379             args.extend(extra_postargs)
    380 
    381             try:
    382                 self.spawn(args)
    383             except DistutilsExecError as msg:
    384                 raise CompileError(msg)
    385 
    386         return objects
    387 
    388 
    389     def create_static_lib(self,
    390                           objects,
    391                           output_libname,
    392                           output_dir=None,
    393                           debug=0,
    394                           target_lang=None):
    395 
    396         if not self.initialized:
    397             self.initialize()
    398         objects, output_dir = self._fix_object_args(objects, output_dir)
    399         output_filename = self.library_filename(output_libname,
    400                                                 output_dir=output_dir)
    401 
    402         if self._need_link(objects, output_filename):
    403             lib_args = objects + ['/OUT:' + output_filename]
    404             if debug:
    405                 pass # XXX what goes here?
    406             try:
    407                 log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
    408                 self.spawn([self.lib] + lib_args)
    409             except DistutilsExecError as msg:
    410                 raise LibError(msg)
    411         else:
    412             log.debug("skipping %s (up-to-date)", output_filename)
    413 
    414 
    415     def link(self,
    416              target_desc,
    417              objects,
    418              output_filename,
    419              output_dir=None,
    420              libraries=None,
    421              library_dirs=None,
    422              runtime_library_dirs=None,
    423              export_symbols=None,
    424              debug=0,
    425              extra_preargs=None,
    426              extra_postargs=None,
    427              build_temp=None,
    428              target_lang=None):
    429 
    430         if not self.initialized:
    431             self.initialize()
    432         objects, output_dir = self._fix_object_args(objects, output_dir)
    433         fixed_args = self._fix_lib_args(libraries, library_dirs,
    434                                         runtime_library_dirs)
    435         libraries, library_dirs, runtime_library_dirs = fixed_args
    436 
    437         if runtime_library_dirs:
    438             self.warn("I don't know what to do with 'runtime_library_dirs': "
    439                        + str(runtime_library_dirs))
    440 
    441         lib_opts = gen_lib_options(self,
    442                                    library_dirs, runtime_library_dirs,
    443                                    libraries)
    444         if output_dir is not None:
    445             output_filename = os.path.join(output_dir, output_filename)
    446 
    447         if self._need_link(objects, output_filename):
    448             ldflags = self._ldflags[target_desc, debug]
    449 
    450             export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]
    451 
    452             ld_args = (ldflags + lib_opts + export_opts +
    453                        objects + ['/OUT:' + output_filename])
    454 
    455             # The MSVC linker generates .lib and .exp files, which cannot be
    456             # suppressed by any linker switches. The .lib files may even be
    457             # needed! Make sure they are generated in the temporary build
    458             # directory. Since they have different names for debug and release
    459             # builds, they can go into the same directory.
    460             build_temp = os.path.dirname(objects[0])
    461             if export_symbols is not None:
    462                 (dll_name, dll_ext) = os.path.splitext(
    463                     os.path.basename(output_filename))
    464                 implib_file = os.path.join(
    465                     build_temp,
    466                     self.library_filename(dll_name))
    467                 ld_args.append ('/IMPLIB:' + implib_file)
    468 
    469             if extra_preargs:
    470                 ld_args[:0] = extra_preargs
    471             if extra_postargs:
    472                 ld_args.extend(extra_postargs)
    473 
    474             output_dir = os.path.dirname(os.path.abspath(output_filename))
    475             self.mkpath(output_dir)
    476             try:
    477                 log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))
    478                 self.spawn([self.linker] + ld_args)
    479                 self._copy_vcruntime(output_dir)
    480             except DistutilsExecError as msg:
    481                 raise LinkError(msg)
    482         else:
    483             log.debug("skipping %s (up-to-date)", output_filename)
    484 
    485     def _copy_vcruntime(self, output_dir):
    486         vcruntime = self._vcruntime_redist
    487         if not vcruntime or not os.path.isfile(vcruntime):
    488             return
    489 
    490         if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS:
    491             return
    492 
    493         log.debug('Copying "%s"', vcruntime)
    494         vcruntime = shutil.copy(vcruntime, output_dir)
    495         os.chmod(vcruntime, stat.S_IWRITE)
    496 
    497     def spawn(self, cmd):
    498         old_path = os.getenv('path')
    499         try:
    500             os.environ['path'] = self._paths
    501             return super().spawn(cmd)
    502         finally:
    503             os.environ['path'] = old_path
    504 
    505     # -- Miscellaneous methods -----------------------------------------
    506     # These are all used by the 'gen_lib_options() function, in
    507     # ccompiler.py.
    508 
    509     def library_dir_option(self, dir):
    510         return "/LIBPATH:" + dir
    511 
    512     def runtime_library_dir_option(self, dir):
    513         raise DistutilsPlatformError(
    514               "don't know how to set runtime library search path for MSVC")
    515 
    516     def library_option(self, lib):
    517         return self.library_filename(lib)
    518 
    519     def find_library_file(self, dirs, lib, debug=0):
    520         # Prefer a debugging library if found (and requested), but deal
    521         # with it if we don't have one.
    522         if debug:
    523             try_names = [lib + "_d", lib]
    524         else:
    525             try_names = [lib]
    526         for dir in dirs:
    527             for name in try_names:
    528                 libfile = os.path.join(dir, self.library_filename(name))
    529                 if os.path.isfile(libfile):
    530                     return libfile
    531         else:
    532             # Oops, didn't find it in *any* of 'dirs'
    533             return None
    534