Path: blob/main/Tools/c-analyzer/c_parser/preprocessor/common.py
12 views
import contextlib1import distutils.ccompiler2import logging3import os4import shlex5import subprocess6import sys78from ..info import FileInfo, SourceLine9from .errors import (10PreprocessorFailure,11ErrorDirectiveError,12MissingDependenciesError,13OSMismatchError,14)151617logger = logging.getLogger(__name__)181920# XXX Add aggregate "source" class(es)?21# * expose all lines as single text string22# * expose all lines as sequence23# * iterate all lines242526def run_cmd(argv, *,27#capture_output=True,28stdout=subprocess.PIPE,29#stderr=subprocess.STDOUT,30stderr=subprocess.PIPE,31text=True,32check=True,33**kwargs34):35if isinstance(stderr, str) and stderr.lower() == 'stdout':36stderr = subprocess.STDOUT3738kw = dict(locals())39kw.pop('argv')40kw.pop('kwargs')41kwargs.update(kw)4243# Remove LANG environment variable: the C parser doesn't support GCC44# localized messages45env = dict(os.environ)46env.pop('LANG', None)4748proc = subprocess.run(argv, env=env, **kwargs)49return proc.stdout505152def preprocess(tool, filename, cwd=None, **kwargs):53argv = _build_argv(tool, filename, **kwargs)54logger.debug(' '.join(shlex.quote(v) for v in argv))5556# Make sure the OS is supported for this file.57if (_expected := is_os_mismatch(filename)):58error = None59raise OSMismatchError(filename, _expected, argv, error, TOOL)6061# Run the command.62with converted_error(tool, argv, filename):63# We use subprocess directly here, instead of calling the64# distutil compiler object's preprocess() method, since that65# one writes to stdout/stderr and it's simpler to do it directly66# through subprocess.67return run_cmd(argv, cwd=cwd)686970def _build_argv(71tool,72filename,73incldirs=None,74includes=None,75macros=None,76preargs=None,77postargs=None,78executable=None,79compiler=None,80):81if includes:82includes = tuple(f'-include{i}' for i in includes)83postargs = (includes + postargs) if postargs else includes8485compiler = distutils.ccompiler.new_compiler(86compiler=compiler or tool,87)88if executable:89compiler.set_executable('preprocessor', executable)9091argv = None92def _spawn(_argv):93nonlocal argv94argv = _argv95compiler.spawn = _spawn96compiler.preprocess(97filename,98macros=[tuple(v) for v in macros or ()],99include_dirs=incldirs or (),100extra_preargs=preargs or (),101extra_postargs=postargs or (),102)103return argv104105106@contextlib.contextmanager107def converted_error(tool, argv, filename):108try:109yield110except subprocess.CalledProcessError as exc:111convert_error(112tool,113argv,114filename,115exc.stderr,116exc.returncode,117)118119120def convert_error(tool, argv, filename, stderr, rc):121error = (stderr.splitlines()[0], rc)122if (_expected := is_os_mismatch(filename, stderr)):123logger.info(stderr.strip())124raise OSMismatchError(filename, _expected, argv, error, tool)125elif (_missing := is_missing_dep(stderr)):126logger.info(stderr.strip())127raise MissingDependenciesError(filename, (_missing,), argv, error, tool)128elif '#error' in stderr:129# XXX Ignore incompatible files.130error = (stderr.splitlines()[1], rc)131logger.info(stderr.strip())132raise ErrorDirectiveError(filename, argv, error, tool)133else:134# Try one more time, with stderr written to the terminal.135try:136output = run_cmd(argv, stderr=None)137except subprocess.CalledProcessError:138raise PreprocessorFailure(filename, argv, error, tool)139140141def is_os_mismatch(filename, errtext=None):142# See: https://docs.python.org/3/library/sys.html#sys.platform143actual = sys.platform144if actual == 'unknown':145raise NotImplementedError146147if errtext is not None:148if (missing := is_missing_dep(errtext)):149matching = get_matching_oses(missing, filename)150if actual not in matching:151return matching152return False153154155def get_matching_oses(missing, filename):156# OSX157if 'darwin' in filename or 'osx' in filename:158return ('darwin',)159elif missing == 'SystemConfiguration/SystemConfiguration.h':160return ('darwin',)161162# Windows163elif missing in ('windows.h', 'winsock2.h'):164return ('win32',)165166# other167elif missing == 'sys/ldr.h':168return ('aix',)169elif missing == 'dl.h':170# XXX The existence of Python/dynload_dl.c implies others...171# Note that hpux isn't actual supported any more.172return ('hpux', '???')173174# unrecognized175else:176return ()177178179def is_missing_dep(errtext):180if 'No such file or directory' in errtext:181missing = errtext.split(': No such file or directory')[0].split()[-1]182return missing183return False184185186