(root)/
Python-3.12.0/
Tools/
c-analyzer/
c_parser/
preprocessor/
common.py
       1  import contextlib
       2  import distutils.ccompiler
       3  import logging
       4  import os
       5  import shlex
       6  import subprocess
       7  import sys
       8  
       9  from ..info import FileInfo, SourceLine
      10  from .errors import (
      11      PreprocessorFailure,
      12      ErrorDirectiveError,
      13      MissingDependenciesError,
      14      OSMismatchError,
      15  )
      16  
      17  
      18  logger = logging.getLogger(__name__)
      19  
      20  
      21  # XXX Add aggregate "source" class(es)?
      22  #  * expose all lines as single text string
      23  #  * expose all lines as sequence
      24  #  * iterate all lines
      25  
      26  
      27  def run_cmd(argv, *,
      28              #capture_output=True,
      29              stdout=subprocess.PIPE,
      30              #stderr=subprocess.STDOUT,
      31              stderr=subprocess.PIPE,
      32              text=True,
      33              check=True,
      34              **kwargs
      35              ):
      36      if isinstance(stderr, str) and stderr.lower() == 'stdout':
      37          stderr = subprocess.STDOUT
      38  
      39      kw = dict(locals())
      40      kw.pop('argv')
      41      kw.pop('kwargs')
      42      kwargs.update(kw)
      43  
      44      # Remove LANG environment variable: the C parser doesn't support GCC
      45      # localized messages
      46      env = dict(os.environ)
      47      env.pop('LANG', None)
      48  
      49      proc = subprocess.run(argv, env=env, **kwargs)
      50      return proc.stdout
      51  
      52  
      53  def preprocess(tool, filename, cwd=None, **kwargs):
      54      argv = _build_argv(tool, filename, **kwargs)
      55      logger.debug(' '.join(shlex.quote(v) for v in argv))
      56  
      57      # Make sure the OS is supported for this file.
      58      if (_expected := is_os_mismatch(filename)):
      59          error = None
      60          raise OSMismatchError(filename, _expected, argv, error, TOOL)
      61  
      62      # Run the command.
      63      with converted_error(tool, argv, filename):
      64          # We use subprocess directly here, instead of calling the
      65          # distutil compiler object's preprocess() method, since that
      66          # one writes to stdout/stderr and it's simpler to do it directly
      67          # through subprocess.
      68          return run_cmd(argv, cwd=cwd)
      69  
      70  
      71  def _build_argv(
      72      tool,
      73      filename,
      74      incldirs=None,
      75      includes=None,
      76      macros=None,
      77      preargs=None,
      78      postargs=None,
      79      executable=None,
      80      compiler=None,
      81  ):
      82      if includes:
      83          includes = tuple(f'-include{i}' for i in includes)
      84          postargs = (includes + postargs) if postargs else includes
      85  
      86      compiler = distutils.ccompiler.new_compiler(
      87          compiler=compiler or tool,
      88      )
      89      if executable:
      90          compiler.set_executable('preprocessor', executable)
      91  
      92      argv = None
      93      def _spawn(_argv):
      94          nonlocal argv
      95          argv = _argv
      96      compiler.spawn = _spawn
      97      compiler.preprocess(
      98          filename,
      99          macros=[tuple(v) for v in macros or ()],
     100          include_dirs=incldirs or (),
     101          extra_preargs=preargs or (),
     102          extra_postargs=postargs or (),
     103      )
     104      return argv
     105  
     106  
     107  @contextlib.contextmanager
     108  def converted_error(tool, argv, filename):
     109      try:
     110          yield
     111      except subprocess.CalledProcessError as exc:
     112          convert_error(
     113              tool,
     114              argv,
     115              filename,
     116              exc.stderr,
     117              exc.returncode,
     118          )
     119  
     120  
     121  def convert_error(tool, argv, filename, stderr, rc):
     122      error = (stderr.splitlines()[0], rc)
     123      if (_expected := is_os_mismatch(filename, stderr)):
     124          logger.info(stderr.strip())
     125          raise OSMismatchError(filename, _expected, argv, error, tool)
     126      elif (_missing := is_missing_dep(stderr)):
     127          logger.info(stderr.strip())
     128          raise MissingDependenciesError(filename, (_missing,), argv, error, tool)
     129      elif '#error' in stderr:
     130          # XXX Ignore incompatible files.
     131          error = (stderr.splitlines()[1], rc)
     132          logger.info(stderr.strip())
     133          raise ErrorDirectiveError(filename, argv, error, tool)
     134      else:
     135          # Try one more time, with stderr written to the terminal.
     136          try:
     137              output = run_cmd(argv, stderr=None)
     138          except subprocess.CalledProcessError:
     139              raise PreprocessorFailure(filename, argv, error, tool)
     140  
     141  
     142  def is_os_mismatch(filename, errtext=None):
     143      # See: https://docs.python.org/3/library/sys.html#sys.platform
     144      actual = sys.platform
     145      if actual == 'unknown':
     146          raise NotImplementedError
     147  
     148      if errtext is not None:
     149          if (missing := is_missing_dep(errtext)):
     150              matching = get_matching_oses(missing, filename)
     151              if actual not in matching:
     152                  return matching
     153      return False
     154  
     155  
     156  def get_matching_oses(missing, filename):
     157      # OSX
     158      if 'darwin' in filename or 'osx' in filename:
     159          return ('darwin',)
     160      elif missing == 'SystemConfiguration/SystemConfiguration.h':
     161          return ('darwin',)
     162  
     163      # Windows
     164      elif missing in ('windows.h', 'winsock2.h'):
     165          return ('win32',)
     166  
     167      # other
     168      elif missing == 'sys/ldr.h':
     169          return ('aix',)
     170      elif missing == 'dl.h':
     171          # XXX The existence of Python/dynload_dl.c implies others...
     172          # Note that hpux isn't actual supported any more.
     173          return ('hpux', '???')
     174  
     175      # unrecognized
     176      else:
     177          return ()
     178  
     179  
     180  def is_missing_dep(errtext):
     181      if 'No such file or directory' in errtext:
     182          missing = errtext.split(': No such file or directory')[0].split()[-1]
     183          return missing
     184      return False