python (3.11.7)
       1  """distutils.command.build_clib
       2  
       3  Implements the Distutils 'build_clib' command, to build a C/C++ library
       4  that is included in the module distribution and needed by an extension
       5  module."""
       6  
       7  
       8  # XXX this module has *lots* of code ripped-off quite transparently from
       9  # build_ext.py -- not surprisingly really, as the work required to build
      10  # a static library from a collection of C source files is not really all
      11  # that different from what's required to build a shared object file from
      12  # a collection of C source files.  Nevertheless, I haven't done the
      13  # necessary refactoring to account for the overlap in code between the
      14  # two modules, mainly because a number of subtle details changed in the
      15  # cut 'n paste.  Sigh.
      16  
      17  import os
      18  from distutils.core import Command
      19  from distutils.errors import DistutilsSetupError
      20  from distutils.sysconfig import customize_compiler
      21  from distutils import log
      22  
      23  
      24  def show_compilers():
      25      from distutils.ccompiler import show_compilers
      26  
      27      show_compilers()
      28  
      29  
      30  class ESC[4;38;5;81mbuild_clib(ESC[4;38;5;149mCommand):
      31  
      32      description = "build C/C++ libraries used by Python extensions"
      33  
      34      user_options = [
      35          ('build-clib=', 'b', "directory to build C/C++ libraries to"),
      36          ('build-temp=', 't', "directory to put temporary build by-products"),
      37          ('debug', 'g', "compile with debugging information"),
      38          ('force', 'f', "forcibly build everything (ignore file timestamps)"),
      39          ('compiler=', 'c', "specify the compiler type"),
      40      ]
      41  
      42      boolean_options = ['debug', 'force']
      43  
      44      help_options = [
      45          ('help-compiler', None, "list available compilers", show_compilers),
      46      ]
      47  
      48      def initialize_options(self):
      49          self.build_clib = None
      50          self.build_temp = None
      51  
      52          # List of libraries to build
      53          self.libraries = None
      54  
      55          # Compilation options for all libraries
      56          self.include_dirs = None
      57          self.define = None
      58          self.undef = None
      59          self.debug = None
      60          self.force = 0
      61          self.compiler = None
      62  
      63      def finalize_options(self):
      64          # This might be confusing: both build-clib and build-temp default
      65          # to build-temp as defined by the "build" command.  This is because
      66          # I think that C libraries are really just temporary build
      67          # by-products, at least from the point of view of building Python
      68          # extensions -- but I want to keep my options open.
      69          self.set_undefined_options(
      70              'build',
      71              ('build_temp', 'build_clib'),
      72              ('build_temp', 'build_temp'),
      73              ('compiler', 'compiler'),
      74              ('debug', 'debug'),
      75              ('force', 'force'),
      76          )
      77  
      78          self.libraries = self.distribution.libraries
      79          if self.libraries:
      80              self.check_library_list(self.libraries)
      81  
      82          if self.include_dirs is None:
      83              self.include_dirs = self.distribution.include_dirs or []
      84          if isinstance(self.include_dirs, str):
      85              self.include_dirs = self.include_dirs.split(os.pathsep)
      86  
      87          # XXX same as for build_ext -- what about 'self.define' and
      88          # 'self.undef' ?
      89  
      90      def run(self):
      91          if not self.libraries:
      92              return
      93  
      94          # Yech -- this is cut 'n pasted from build_ext.py!
      95          from distutils.ccompiler import new_compiler
      96  
      97          self.compiler = new_compiler(
      98              compiler=self.compiler, dry_run=self.dry_run, force=self.force
      99          )
     100          customize_compiler(self.compiler)
     101  
     102          if self.include_dirs is not None:
     103              self.compiler.set_include_dirs(self.include_dirs)
     104          if self.define is not None:
     105              # 'define' option is a list of (name,value) tuples
     106              for (name, value) in self.define:
     107                  self.compiler.define_macro(name, value)
     108          if self.undef is not None:
     109              for macro in self.undef:
     110                  self.compiler.undefine_macro(macro)
     111  
     112          self.build_libraries(self.libraries)
     113  
     114      def check_library_list(self, libraries):
     115          """Ensure that the list of libraries is valid.
     116  
     117          `library` is presumably provided as a command option 'libraries'.
     118          This method checks that it is a list of 2-tuples, where the tuples
     119          are (library_name, build_info_dict).
     120  
     121          Raise DistutilsSetupError if the structure is invalid anywhere;
     122          just returns otherwise.
     123          """
     124          if not isinstance(libraries, list):
     125              raise DistutilsSetupError("'libraries' option must be a list of tuples")
     126  
     127          for lib in libraries:
     128              if not isinstance(lib, tuple) and len(lib) != 2:
     129                  raise DistutilsSetupError("each element of 'libraries' must a 2-tuple")
     130  
     131              name, build_info = lib
     132  
     133              if not isinstance(name, str):
     134                  raise DistutilsSetupError(
     135                      "first element of each tuple in 'libraries' "
     136                      "must be a string (the library name)"
     137                  )
     138  
     139              if '/' in name or (os.sep != '/' and os.sep in name):
     140                  raise DistutilsSetupError(
     141                      "bad library name '%s': "
     142                      "may not contain directory separators" % lib[0]
     143                  )
     144  
     145              if not isinstance(build_info, dict):
     146                  raise DistutilsSetupError(
     147                      "second element of each tuple in 'libraries' "
     148                      "must be a dictionary (build info)"
     149                  )
     150  
     151      def get_library_names(self):
     152          # Assume the library list is valid -- 'check_library_list()' is
     153          # called from 'finalize_options()', so it should be!
     154          if not self.libraries:
     155              return None
     156  
     157          lib_names = []
     158          for (lib_name, build_info) in self.libraries:
     159              lib_names.append(lib_name)
     160          return lib_names
     161  
     162      def get_source_files(self):
     163          self.check_library_list(self.libraries)
     164          filenames = []
     165          for (lib_name, build_info) in self.libraries:
     166              sources = build_info.get('sources')
     167              if sources is None or not isinstance(sources, (list, tuple)):
     168                  raise DistutilsSetupError(
     169                      "in 'libraries' option (library '%s'), "
     170                      "'sources' must be present and must be "
     171                      "a list of source filenames" % lib_name
     172                  )
     173  
     174              filenames.extend(sources)
     175          return filenames
     176  
     177      def build_libraries(self, libraries):
     178          for (lib_name, build_info) in libraries:
     179              sources = build_info.get('sources')
     180              if sources is None or not isinstance(sources, (list, tuple)):
     181                  raise DistutilsSetupError(
     182                      "in 'libraries' option (library '%s'), "
     183                      "'sources' must be present and must be "
     184                      "a list of source filenames" % lib_name
     185                  )
     186              sources = list(sources)
     187  
     188              log.info("building '%s' library", lib_name)
     189  
     190              # First, compile the source code to object files in the library
     191              # directory.  (This should probably change to putting object
     192              # files in a temporary build directory.)
     193              macros = build_info.get('macros')
     194              include_dirs = build_info.get('include_dirs')
     195              objects = self.compiler.compile(
     196                  sources,
     197                  output_dir=self.build_temp,
     198                  macros=macros,
     199                  include_dirs=include_dirs,
     200                  debug=self.debug,
     201              )
     202  
     203              # Now "link" the object files together into a static library.
     204              # (On Unix at least, this isn't really linking -- it just
     205              # builds an archive.  Whatever.)
     206              self.compiler.create_static_lib(
     207                  objects, lib_name, output_dir=self.build_clib, debug=self.debug
     208              )