(root)/
Python-3.11.7/
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, **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)
      69  
      70  
      71  def _build_argv(
      72      tool,
      73      filename,
      74      incldirs=None,
      75      macros=None,
      76      preargs=None,
      77      postargs=None,
      78      executable=None,
      79      compiler=None,
      80  ):
      81      compiler = distutils.ccompiler.new_compiler(
      82          compiler=compiler or tool,
      83      )
      84      if executable:
      85          compiler.set_executable('preprocessor', executable)
      86  
      87      argv = None
      88      def _spawn(_argv):
      89          nonlocal argv
      90          argv = _argv
      91      compiler.spawn = _spawn
      92      compiler.preprocess(
      93          filename,
      94          macros=[tuple(v) for v in macros or ()],
      95          include_dirs=incldirs or (),
      96          extra_preargs=preargs or (),
      97          extra_postargs=postargs or (),
      98      )
      99      return argv
     100  
     101  
     102  @contextlib.contextmanager
     103  def converted_error(tool, argv, filename):
     104      try:
     105          yield
     106      except subprocess.CalledProcessError as exc:
     107          convert_error(
     108              tool,
     109              argv,
     110              filename,
     111              exc.stderr,
     112              exc.returncode,
     113          )
     114  
     115  
     116  def convert_error(tool, argv, filename, stderr, rc):
     117      error = (stderr.splitlines()[0], rc)
     118      if (_expected := is_os_mismatch(filename, stderr)):
     119          logger.debug(stderr.strip())
     120          raise OSMismatchError(filename, _expected, argv, error, tool)
     121      elif (_missing := is_missing_dep(stderr)):
     122          logger.debug(stderr.strip())
     123          raise MissingDependenciesError(filename, (_missing,), argv, error, tool)
     124      elif '#error' in stderr:
     125          # XXX Ignore incompatible files.
     126          error = (stderr.splitlines()[1], rc)
     127          logger.debug(stderr.strip())
     128          raise ErrorDirectiveError(filename, argv, error, tool)
     129      else:
     130          # Try one more time, with stderr written to the terminal.
     131          try:
     132              output = run_cmd(argv, stderr=None)
     133          except subprocess.CalledProcessError:
     134              raise PreprocessorFailure(filename, argv, error, tool)
     135  
     136  
     137  def is_os_mismatch(filename, errtext=None):
     138      # See: https://docs.python.org/3/library/sys.html#sys.platform
     139      actual = sys.platform
     140      if actual == 'unknown':
     141          raise NotImplementedError
     142  
     143      if errtext is not None:
     144          if (missing := is_missing_dep(errtext)):
     145              matching = get_matching_oses(missing, filename)
     146              if actual not in matching:
     147                  return matching
     148      return False
     149  
     150  
     151  def get_matching_oses(missing, filename):
     152      # OSX
     153      if 'darwin' in filename or 'osx' in filename:
     154          return ('darwin',)
     155      elif missing == 'SystemConfiguration/SystemConfiguration.h':
     156          return ('darwin',)
     157  
     158      # Windows
     159      elif missing in ('windows.h', 'winsock2.h'):
     160          return ('win32',)
     161  
     162      # other
     163      elif missing == 'sys/ldr.h':
     164          return ('aix',)
     165      elif missing == 'dl.h':
     166          # XXX The existence of Python/dynload_dl.c implies others...
     167          # Note that hpux isn't actual supported any more.
     168          return ('hpux', '???')
     169  
     170      # unrecognized
     171      else:
     172          return ()
     173  
     174  
     175  def is_missing_dep(errtext):
     176      if 'No such file or directory' in errtext:
     177          missing = errtext.split(': No such file or directory')[0].split()[-1]
     178          return missing
     179      return False