python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
_distutils/
unixccompiler.py
       1  """distutils.unixccompiler
       2  
       3  Contains the UnixCCompiler class, a subclass of CCompiler that handles
       4  the "typical" Unix-style command-line C compiler:
       5    * macros defined with -Dname[=value]
       6    * macros undefined with -Uname
       7    * include search directories specified with -Idir
       8    * libraries specified with -lllib
       9    * library search directories specified with -Ldir
      10    * compile handled by 'cc' (or similar) executable with -c option:
      11      compiles .c to .o
      12    * link static library handled by 'ar' command (possibly with 'ranlib')
      13    * link shared library handled by 'cc -shared'
      14  """
      15  
      16  import os
      17  import sys
      18  import re
      19  import shlex
      20  import itertools
      21  
      22  from distutils import sysconfig
      23  from distutils.dep_util import newer
      24  from distutils.ccompiler import CCompiler, gen_preprocess_options, gen_lib_options
      25  from distutils.errors import DistutilsExecError, CompileError, LibError, LinkError
      26  from distutils import log
      27  from ._macos_compat import compiler_fixup
      28  
      29  # XXX Things not currently handled:
      30  #   * optimization/debug/warning flags; we just use whatever's in Python's
      31  #     Makefile and live with it.  Is this adequate?  If not, we might
      32  #     have to have a bunch of subclasses GNUCCompiler, SGICCompiler,
      33  #     SunCCompiler, and I suspect down that road lies madness.
      34  #   * even if we don't know a warning flag from an optimization flag,
      35  #     we need some way for outsiders to feed preprocessor/compiler/linker
      36  #     flags in to us -- eg. a sysadmin might want to mandate certain flags
      37  #     via a site config file, or a user might want to set something for
      38  #     compiling this module distribution only via the setup.py command
      39  #     line, whatever.  As long as these options come from something on the
      40  #     current system, they can be as system-dependent as they like, and we
      41  #     should just happily stuff them into the preprocessor/compiler/linker
      42  #     options and carry on.
      43  
      44  
      45  def _split_env(cmd):
      46      """
      47      For macOS, split command into 'env' portion (if any)
      48      and the rest of the linker command.
      49  
      50      >>> _split_env(['a', 'b', 'c'])
      51      ([], ['a', 'b', 'c'])
      52      >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])
      53      (['/usr/bin/env', 'A=3'], ['gcc'])
      54      """
      55      pivot = 0
      56      if os.path.basename(cmd[0]) == "env":
      57          pivot = 1
      58          while '=' in cmd[pivot]:
      59              pivot += 1
      60      return cmd[:pivot], cmd[pivot:]
      61  
      62  
      63  def _split_aix(cmd):
      64      """
      65      AIX platforms prefix the compiler with the ld_so_aix
      66      script, so split that from the linker command.
      67  
      68      >>> _split_aix(['a', 'b', 'c'])
      69      ([], ['a', 'b', 'c'])
      70      >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])
      71      (['/bin/foo/ld_so_aix'], ['gcc'])
      72      """
      73      pivot = os.path.basename(cmd[0]) == 'ld_so_aix'
      74      return cmd[:pivot], cmd[pivot:]
      75  
      76  
      77  def _linker_params(linker_cmd, compiler_cmd):
      78      """
      79      The linker command usually begins with the compiler
      80      command (possibly multiple elements), followed by zero or more
      81      params for shared library building.
      82  
      83      If the LDSHARED env variable overrides the linker command,
      84      however, the commands may not match.
      85  
      86      Return the best guess of the linker parameters by stripping
      87      the linker command. If the compiler command does not
      88      match the linker command, assume the linker command is
      89      just the first element.
      90  
      91      >>> _linker_params('gcc foo bar'.split(), ['gcc'])
      92      ['foo', 'bar']
      93      >>> _linker_params('gcc foo bar'.split(), ['other'])
      94      ['foo', 'bar']
      95      >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())
      96      ['foo', 'bar']
      97      >>> _linker_params(['gcc'], ['gcc'])
      98      []
      99      """
     100      c_len = len(compiler_cmd)
     101      pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1
     102      return linker_cmd[pivot:]
     103  
     104  
     105  class ESC[4;38;5;81mUnixCCompiler(ESC[4;38;5;149mCCompiler):
     106  
     107      compiler_type = 'unix'
     108  
     109      # These are used by CCompiler in two places: the constructor sets
     110      # instance attributes 'preprocessor', 'compiler', etc. from them, and
     111      # 'set_executable()' allows any of these to be set.  The defaults here
     112      # are pretty generic; they will probably have to be set by an outsider
     113      # (eg. using information discovered by the sysconfig about building
     114      # Python extensions).
     115      executables = {
     116          'preprocessor': None,
     117          'compiler': ["cc"],
     118          'compiler_so': ["cc"],
     119          'compiler_cxx': ["cc"],
     120          'linker_so': ["cc", "-shared"],
     121          'linker_exe': ["cc"],
     122          'archiver': ["ar", "-cr"],
     123          'ranlib': None,
     124      }
     125  
     126      if sys.platform[:6] == "darwin":
     127          executables['ranlib'] = ["ranlib"]
     128  
     129      # Needed for the filename generation methods provided by the base
     130      # class, CCompiler.  NB. whoever instantiates/uses a particular
     131      # UnixCCompiler instance should set 'shared_lib_ext' -- we set a
     132      # reasonable common default here, but it's not necessarily used on all
     133      # Unices!
     134  
     135      src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]
     136      obj_extension = ".o"
     137      static_lib_extension = ".a"
     138      shared_lib_extension = ".so"
     139      dylib_lib_extension = ".dylib"
     140      xcode_stub_lib_extension = ".tbd"
     141      static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"
     142      xcode_stub_lib_format = dylib_lib_format
     143      if sys.platform == "cygwin":
     144          exe_extension = ".exe"
     145  
     146      def preprocess(
     147          self,
     148          source,
     149          output_file=None,
     150          macros=None,
     151          include_dirs=None,
     152          extra_preargs=None,
     153          extra_postargs=None,
     154      ):
     155          fixed_args = self._fix_compile_args(None, macros, include_dirs)
     156          ignore, macros, include_dirs = fixed_args
     157          pp_opts = gen_preprocess_options(macros, include_dirs)
     158          pp_args = self.preprocessor + pp_opts
     159          if output_file:
     160              pp_args.extend(['-o', output_file])
     161          if extra_preargs:
     162              pp_args[:0] = extra_preargs
     163          if extra_postargs:
     164              pp_args.extend(extra_postargs)
     165          pp_args.append(source)
     166  
     167          # reasons to preprocess:
     168          # - force is indicated
     169          # - output is directed to stdout
     170          # - source file is newer than the target
     171          preprocess = self.force or output_file is None or newer(source, output_file)
     172          if not preprocess:
     173              return
     174  
     175          if output_file:
     176              self.mkpath(os.path.dirname(output_file))
     177  
     178          try:
     179              self.spawn(pp_args)
     180          except DistutilsExecError as msg:
     181              raise CompileError(msg)
     182  
     183      def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
     184          compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)
     185          try:
     186              self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)
     187          except DistutilsExecError as msg:
     188              raise CompileError(msg)
     189  
     190      def create_static_lib(
     191          self, objects, output_libname, output_dir=None, debug=0, target_lang=None
     192      ):
     193          objects, output_dir = self._fix_object_args(objects, output_dir)
     194  
     195          output_filename = self.library_filename(output_libname, output_dir=output_dir)
     196  
     197          if self._need_link(objects, output_filename):
     198              self.mkpath(os.path.dirname(output_filename))
     199              self.spawn(self.archiver + [output_filename] + objects + self.objects)
     200  
     201              # Not many Unices required ranlib anymore -- SunOS 4.x is, I
     202              # think the only major Unix that does.  Maybe we need some
     203              # platform intelligence here to skip ranlib if it's not
     204              # needed -- or maybe Python's configure script took care of
     205              # it for us, hence the check for leading colon.
     206              if self.ranlib:
     207                  try:
     208                      self.spawn(self.ranlib + [output_filename])
     209                  except DistutilsExecError as msg:
     210                      raise LibError(msg)
     211          else:
     212              log.debug("skipping %s (up-to-date)", output_filename)
     213  
     214      def link(
     215          self,
     216          target_desc,
     217          objects,
     218          output_filename,
     219          output_dir=None,
     220          libraries=None,
     221          library_dirs=None,
     222          runtime_library_dirs=None,
     223          export_symbols=None,
     224          debug=0,
     225          extra_preargs=None,
     226          extra_postargs=None,
     227          build_temp=None,
     228          target_lang=None,
     229      ):
     230          objects, output_dir = self._fix_object_args(objects, output_dir)
     231          fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
     232          libraries, library_dirs, runtime_library_dirs = fixed_args
     233  
     234          lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
     235          if not isinstance(output_dir, (str, type(None))):
     236              raise TypeError("'output_dir' must be a string or None")
     237          if output_dir is not None:
     238              output_filename = os.path.join(output_dir, output_filename)
     239  
     240          if self._need_link(objects, output_filename):
     241              ld_args = objects + self.objects + lib_opts + ['-o', output_filename]
     242              if debug:
     243                  ld_args[:0] = ['-g']
     244              if extra_preargs:
     245                  ld_args[:0] = extra_preargs
     246              if extra_postargs:
     247                  ld_args.extend(extra_postargs)
     248              self.mkpath(os.path.dirname(output_filename))
     249              try:
     250                  # Select a linker based on context: linker_exe when
     251                  # building an executable or linker_so (with shared options)
     252                  # when building a shared library.
     253                  building_exe = target_desc == CCompiler.EXECUTABLE
     254                  linker = (self.linker_exe if building_exe else self.linker_so)[:]
     255  
     256                  if target_lang == "c++" and self.compiler_cxx:
     257                      env, linker_ne = _split_env(linker)
     258                      aix, linker_na = _split_aix(linker_ne)
     259                      _, compiler_cxx_ne = _split_env(self.compiler_cxx)
     260                      _, linker_exe_ne = _split_env(self.linker_exe)
     261  
     262                      params = _linker_params(linker_na, linker_exe_ne)
     263                      linker = env + aix + compiler_cxx_ne + params
     264  
     265                  linker = compiler_fixup(linker, ld_args)
     266  
     267                  self.spawn(linker + ld_args)
     268              except DistutilsExecError as msg:
     269                  raise LinkError(msg)
     270          else:
     271              log.debug("skipping %s (up-to-date)", output_filename)
     272  
     273      # -- Miscellaneous methods -----------------------------------------
     274      # These are all used by the 'gen_lib_options() function, in
     275      # ccompiler.py.
     276  
     277      def library_dir_option(self, dir):
     278          return "-L" + dir
     279  
     280      def _is_gcc(self):
     281          cc_var = sysconfig.get_config_var("CC")
     282          compiler = os.path.basename(shlex.split(cc_var)[0])
     283          return "gcc" in compiler or "g++" in compiler
     284  
     285      def runtime_library_dir_option(self, dir):
     286          # XXX Hackish, at the very least.  See Python bug #445902:
     287          # http://sourceforge.net/tracker/index.php
     288          #   ?func=detail&aid=445902&group_id=5470&atid=105470
     289          # Linkers on different platforms need different options to
     290          # specify that directories need to be added to the list of
     291          # directories searched for dependencies when a dynamic library
     292          # is sought.  GCC on GNU systems (Linux, FreeBSD, ...) has to
     293          # be told to pass the -R option through to the linker, whereas
     294          # other compilers and gcc on other systems just know this.
     295          # Other compilers may need something slightly different.  At
     296          # this time, there's no way to determine this information from
     297          # the configuration data stored in the Python installation, so
     298          # we use this hack.
     299          if sys.platform[:6] == "darwin":
     300              from distutils.util import get_macosx_target_ver, split_version
     301  
     302              macosx_target_ver = get_macosx_target_ver()
     303              if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:
     304                  return "-Wl,-rpath," + dir
     305              else:  # no support for -rpath on earlier macOS versions
     306                  return "-L" + dir
     307          elif sys.platform[:7] == "freebsd":
     308              return "-Wl,-rpath=" + dir
     309          elif sys.platform[:5] == "hp-ux":
     310              return [
     311                  "-Wl,+s" if self._is_gcc() else "+s",
     312                  "-L" + dir,
     313              ]
     314  
     315          # For all compilers, `-Wl` is the presumed way to
     316          # pass a compiler option to the linker and `-R` is
     317          # the way to pass an RPATH.
     318          if sysconfig.get_config_var("GNULD") == "yes":
     319              # GNU ld needs an extra option to get a RUNPATH
     320              # instead of just an RPATH.
     321              return "-Wl,--enable-new-dtags,-R" + dir
     322          else:
     323              return "-Wl,-R" + dir
     324  
     325      def library_option(self, lib):
     326          return "-l" + lib
     327  
     328      @staticmethod
     329      def _library_root(dir):
     330          """
     331          macOS users can specify an alternate SDK using'-isysroot'.
     332          Calculate the SDK root if it is specified.
     333  
     334          Note that, as of Xcode 7, Apple SDKs may contain textual stub
     335          libraries with .tbd extensions rather than the normal .dylib
     336          shared libraries installed in /.  The Apple compiler tool
     337          chain handles this transparently but it can cause problems
     338          for programs that are being built with an SDK and searching
     339          for specific libraries.  Callers of find_library_file need to
     340          keep in mind that the base filename of the returned SDK library
     341          file might have a different extension from that of the library
     342          file installed on the running system, for example:
     343            /Applications/Xcode.app/Contents/Developer/Platforms/
     344                MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/
     345                usr/lib/libedit.tbd
     346          vs
     347            /usr/lib/libedit.dylib
     348          """
     349          cflags = sysconfig.get_config_var('CFLAGS')
     350          match = re.search(r'-isysroot\s*(\S+)', cflags)
     351  
     352          apply_root = (
     353              sys.platform == 'darwin'
     354              and match
     355              and (
     356                  dir.startswith('/System/')
     357                  or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))
     358              )
     359          )
     360  
     361          return os.path.join(match.group(1), dir[1:]) if apply_root else dir
     362  
     363      def find_library_file(self, dirs, lib, debug=0):
     364          r"""
     365          Second-guess the linker with not much hard
     366          data to go on: GCC seems to prefer the shared library, so
     367          assume that *all* Unix C compilers do,
     368          ignoring even GCC's "-static" option.
     369  
     370          >>> compiler = UnixCCompiler()
     371          >>> compiler._library_root = lambda dir: dir
     372          >>> monkeypatch = getfixture('monkeypatch')
     373          >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)
     374          >>> dirs = ('/foo/bar/missing', '/foo/bar/existing')
     375          >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
     376          '/foo/bar/existing/libabc.dylib'
     377          >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
     378          '/foo/bar/existing/libabc.dylib'
     379          >>> monkeypatch.setattr(os.path, 'exists',
     380          ...     lambda d: 'existing' in d and '.a' in d)
     381          >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
     382          '/foo/bar/existing/libabc.a'
     383          >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
     384          '/foo/bar/existing/libabc.a'
     385          """
     386          lib_names = (
     387              self.library_filename(lib, lib_type=type)
     388              for type in 'dylib xcode_stub shared static'.split()
     389          )
     390  
     391          roots = map(self._library_root, dirs)
     392  
     393          searched = (
     394              os.path.join(root, lib_name)
     395              for root, lib_name in itertools.product(roots, lib_names)
     396          )
     397  
     398          found = filter(os.path.exists, searched)
     399  
     400          # Return None if it could not be found in any dir.
     401          return next(found, None)