python (3.11.7)
       1  """distutils.command.config
       2  
       3  Implements the Distutils 'config' command, a (mostly) empty command class
       4  that exists mainly to be sub-classed by specific module distributions and
       5  applications.  The idea is that while every "config" command is different,
       6  at least they're all named the same, and users always see "config" in the
       7  list of standard commands.  Also, this is a good place to put common
       8  configure-like tasks: "try to compile this C code", or "figure out where
       9  this header file lives".
      10  """
      11  
      12  import os
      13  import re
      14  
      15  from distutils.core import Command
      16  from distutils.errors import DistutilsExecError
      17  from distutils.sysconfig import customize_compiler
      18  from distutils import log
      19  
      20  LANG_EXT = {"c": ".c", "c++": ".cxx"}
      21  
      22  
      23  class ESC[4;38;5;81mconfig(ESC[4;38;5;149mCommand):
      24  
      25      description = "prepare to build"
      26  
      27      user_options = [
      28          ('compiler=', None, "specify the compiler type"),
      29          ('cc=', None, "specify the compiler executable"),
      30          ('include-dirs=', 'I', "list of directories to search for header files"),
      31          ('define=', 'D', "C preprocessor macros to define"),
      32          ('undef=', 'U', "C preprocessor macros to undefine"),
      33          ('libraries=', 'l', "external C libraries to link with"),
      34          ('library-dirs=', 'L', "directories to search for external C libraries"),
      35          ('noisy', None, "show every action (compile, link, run, ...) taken"),
      36          (
      37              'dump-source',
      38              None,
      39              "dump generated source files before attempting to compile them",
      40          ),
      41      ]
      42  
      43      # The three standard command methods: since the "config" command
      44      # does nothing by default, these are empty.
      45  
      46      def initialize_options(self):
      47          self.compiler = None
      48          self.cc = None
      49          self.include_dirs = None
      50          self.libraries = None
      51          self.library_dirs = None
      52  
      53          # maximal output for now
      54          self.noisy = 1
      55          self.dump_source = 1
      56  
      57          # list of temporary files generated along-the-way that we have
      58          # to clean at some point
      59          self.temp_files = []
      60  
      61      def finalize_options(self):
      62          if self.include_dirs is None:
      63              self.include_dirs = self.distribution.include_dirs or []
      64          elif isinstance(self.include_dirs, str):
      65              self.include_dirs = self.include_dirs.split(os.pathsep)
      66  
      67          if self.libraries is None:
      68              self.libraries = []
      69          elif isinstance(self.libraries, str):
      70              self.libraries = [self.libraries]
      71  
      72          if self.library_dirs is None:
      73              self.library_dirs = []
      74          elif isinstance(self.library_dirs, str):
      75              self.library_dirs = self.library_dirs.split(os.pathsep)
      76  
      77      def run(self):
      78          pass
      79  
      80      # Utility methods for actual "config" commands.  The interfaces are
      81      # loosely based on Autoconf macros of similar names.  Sub-classes
      82      # may use these freely.
      83  
      84      def _check_compiler(self):
      85          """Check that 'self.compiler' really is a CCompiler object;
      86          if not, make it one.
      87          """
      88          # We do this late, and only on-demand, because this is an expensive
      89          # import.
      90          from distutils.ccompiler import CCompiler, new_compiler
      91  
      92          if not isinstance(self.compiler, CCompiler):
      93              self.compiler = new_compiler(
      94                  compiler=self.compiler, dry_run=self.dry_run, force=1
      95              )
      96              customize_compiler(self.compiler)
      97              if self.include_dirs:
      98                  self.compiler.set_include_dirs(self.include_dirs)
      99              if self.libraries:
     100                  self.compiler.set_libraries(self.libraries)
     101              if self.library_dirs:
     102                  self.compiler.set_library_dirs(self.library_dirs)
     103  
     104      def _gen_temp_sourcefile(self, body, headers, lang):
     105          filename = "_configtest" + LANG_EXT[lang]
     106          with open(filename, "w") as file:
     107              if headers:
     108                  for header in headers:
     109                      file.write("#include <%s>\n" % header)
     110                  file.write("\n")
     111              file.write(body)
     112              if body[-1] != "\n":
     113                  file.write("\n")
     114          return filename
     115  
     116      def _preprocess(self, body, headers, include_dirs, lang):
     117          src = self._gen_temp_sourcefile(body, headers, lang)
     118          out = "_configtest.i"
     119          self.temp_files.extend([src, out])
     120          self.compiler.preprocess(src, out, include_dirs=include_dirs)
     121          return (src, out)
     122  
     123      def _compile(self, body, headers, include_dirs, lang):
     124          src = self._gen_temp_sourcefile(body, headers, lang)
     125          if self.dump_source:
     126              dump_file(src, "compiling '%s':" % src)
     127          (obj,) = self.compiler.object_filenames([src])
     128          self.temp_files.extend([src, obj])
     129          self.compiler.compile([src], include_dirs=include_dirs)
     130          return (src, obj)
     131  
     132      def _link(self, body, headers, include_dirs, libraries, library_dirs, lang):
     133          (src, obj) = self._compile(body, headers, include_dirs, lang)
     134          prog = os.path.splitext(os.path.basename(src))[0]
     135          self.compiler.link_executable(
     136              [obj],
     137              prog,
     138              libraries=libraries,
     139              library_dirs=library_dirs,
     140              target_lang=lang,
     141          )
     142  
     143          if self.compiler.exe_extension is not None:
     144              prog = prog + self.compiler.exe_extension
     145          self.temp_files.append(prog)
     146  
     147          return (src, obj, prog)
     148  
     149      def _clean(self, *filenames):
     150          if not filenames:
     151              filenames = self.temp_files
     152              self.temp_files = []
     153          log.info("removing: %s", ' '.join(filenames))
     154          for filename in filenames:
     155              try:
     156                  os.remove(filename)
     157              except OSError:
     158                  pass
     159  
     160      # XXX these ignore the dry-run flag: what to do, what to do? even if
     161      # you want a dry-run build, you still need some sort of configuration
     162      # info.  My inclination is to make it up to the real config command to
     163      # consult 'dry_run', and assume a default (minimal) configuration if
     164      # true.  The problem with trying to do it here is that you'd have to
     165      # return either true or false from all the 'try' methods, neither of
     166      # which is correct.
     167  
     168      # XXX need access to the header search path and maybe default macros.
     169  
     170      def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"):
     171          """Construct a source file from 'body' (a string containing lines
     172          of C/C++ code) and 'headers' (a list of header files to include)
     173          and run it through the preprocessor.  Return true if the
     174          preprocessor succeeded, false if there were any errors.
     175          ('body' probably isn't of much use, but what the heck.)
     176          """
     177          from distutils.ccompiler import CompileError
     178  
     179          self._check_compiler()
     180          ok = True
     181          try:
     182              self._preprocess(body, headers, include_dirs, lang)
     183          except CompileError:
     184              ok = False
     185  
     186          self._clean()
     187          return ok
     188  
     189      def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, lang="c"):
     190          """Construct a source file (just like 'try_cpp()'), run it through
     191          the preprocessor, and return true if any line of the output matches
     192          'pattern'.  'pattern' should either be a compiled regex object or a
     193          string containing a regex.  If both 'body' and 'headers' are None,
     194          preprocesses an empty file -- which can be useful to determine the
     195          symbols the preprocessor and compiler set by default.
     196          """
     197          self._check_compiler()
     198          src, out = self._preprocess(body, headers, include_dirs, lang)
     199  
     200          if isinstance(pattern, str):
     201              pattern = re.compile(pattern)
     202  
     203          with open(out) as file:
     204              match = False
     205              while True:
     206                  line = file.readline()
     207                  if line == '':
     208                      break
     209                  if pattern.search(line):
     210                      match = True
     211                      break
     212  
     213          self._clean()
     214          return match
     215  
     216      def try_compile(self, body, headers=None, include_dirs=None, lang="c"):
     217          """Try to compile a source file built from 'body' and 'headers'.
     218          Return true on success, false otherwise.
     219          """
     220          from distutils.ccompiler import CompileError
     221  
     222          self._check_compiler()
     223          try:
     224              self._compile(body, headers, include_dirs, lang)
     225              ok = True
     226          except CompileError:
     227              ok = False
     228  
     229          log.info(ok and "success!" or "failure.")
     230          self._clean()
     231          return ok
     232  
     233      def try_link(
     234          self,
     235          body,
     236          headers=None,
     237          include_dirs=None,
     238          libraries=None,
     239          library_dirs=None,
     240          lang="c",
     241      ):
     242          """Try to compile and link a source file, built from 'body' and
     243          'headers', to executable form.  Return true on success, false
     244          otherwise.
     245          """
     246          from distutils.ccompiler import CompileError, LinkError
     247  
     248          self._check_compiler()
     249          try:
     250              self._link(body, headers, include_dirs, libraries, library_dirs, lang)
     251              ok = True
     252          except (CompileError, LinkError):
     253              ok = False
     254  
     255          log.info(ok and "success!" or "failure.")
     256          self._clean()
     257          return ok
     258  
     259      def try_run(
     260          self,
     261          body,
     262          headers=None,
     263          include_dirs=None,
     264          libraries=None,
     265          library_dirs=None,
     266          lang="c",
     267      ):
     268          """Try to compile, link to an executable, and run a program
     269          built from 'body' and 'headers'.  Return true on success, false
     270          otherwise.
     271          """
     272          from distutils.ccompiler import CompileError, LinkError
     273  
     274          self._check_compiler()
     275          try:
     276              src, obj, exe = self._link(
     277                  body, headers, include_dirs, libraries, library_dirs, lang
     278              )
     279              self.spawn([exe])
     280              ok = True
     281          except (CompileError, LinkError, DistutilsExecError):
     282              ok = False
     283  
     284          log.info(ok and "success!" or "failure.")
     285          self._clean()
     286          return ok
     287  
     288      # -- High-level methods --------------------------------------------
     289      # (these are the ones that are actually likely to be useful
     290      # when implementing a real-world config command!)
     291  
     292      def check_func(
     293          self,
     294          func,
     295          headers=None,
     296          include_dirs=None,
     297          libraries=None,
     298          library_dirs=None,
     299          decl=0,
     300          call=0,
     301      ):
     302          """Determine if function 'func' is available by constructing a
     303          source file that refers to 'func', and compiles and links it.
     304          If everything succeeds, returns true; otherwise returns false.
     305  
     306          The constructed source file starts out by including the header
     307          files listed in 'headers'.  If 'decl' is true, it then declares
     308          'func' (as "int func()"); you probably shouldn't supply 'headers'
     309          and set 'decl' true in the same call, or you might get errors about
     310          a conflicting declarations for 'func'.  Finally, the constructed
     311          'main()' function either references 'func' or (if 'call' is true)
     312          calls it.  'libraries' and 'library_dirs' are used when
     313          linking.
     314          """
     315          self._check_compiler()
     316          body = []
     317          if decl:
     318              body.append("int %s ();" % func)
     319          body.append("int main () {")
     320          if call:
     321              body.append("  %s();" % func)
     322          else:
     323              body.append("  %s;" % func)
     324          body.append("}")
     325          body = "\n".join(body) + "\n"
     326  
     327          return self.try_link(body, headers, include_dirs, libraries, library_dirs)
     328  
     329      def check_lib(
     330          self,
     331          library,
     332          library_dirs=None,
     333          headers=None,
     334          include_dirs=None,
     335          other_libraries=[],
     336      ):
     337          """Determine if 'library' is available to be linked against,
     338          without actually checking that any particular symbols are provided
     339          by it.  'headers' will be used in constructing the source file to
     340          be compiled, but the only effect of this is to check if all the
     341          header files listed are available.  Any libraries listed in
     342          'other_libraries' will be included in the link, in case 'library'
     343          has symbols that depend on other libraries.
     344          """
     345          self._check_compiler()
     346          return self.try_link(
     347              "int main (void) { }",
     348              headers,
     349              include_dirs,
     350              [library] + other_libraries,
     351              library_dirs,
     352          )
     353  
     354      def check_header(self, header, include_dirs=None, library_dirs=None, lang="c"):
     355          """Determine if the system header file named by 'header_file'
     356          exists and can be found by the preprocessor; return true if so,
     357          false otherwise.
     358          """
     359          return self.try_cpp(
     360              body="/* No body */", headers=[header], include_dirs=include_dirs
     361          )
     362  
     363  
     364  def dump_file(filename, head=None):
     365      """Dumps a file content into log.info.
     366  
     367      If head is not None, will be dumped before the file content.
     368      """
     369      if head is None:
     370          log.info('%s', filename)
     371      else:
     372          log.info(head)
     373      file = open(filename)
     374      try:
     375          log.info(file.read())
     376      finally:
     377          file.close()