python (3.11.7)

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