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