Path: blob/main/Tools/c-analyzer/c_parser/preprocessor/__init__.py
12 views
import contextlib1import logging2import os3import os.path4import re5import sys67from c_common.fsutil import match_glob as _match_glob8from c_common.tables import parse_table as _parse_table9from ..source import (10resolve as _resolve_source,11good_file as _good_file,12)13from . import errors as _errors14from . import (15pure as _pure,16gcc as _gcc,17)181920logger = logging.getLogger(__name__)212223# Supported "source":24# * filename (string)25# * lines (iterable)26# * text (string)27# Supported return values:28# * iterator of SourceLine29# * sequence of SourceLine30# * text (string)31# * something that combines all those32# XXX Add the missing support from above.33# XXX Add more low-level functions to handle permutations?3435def preprocess(source, *,36incldirs=None,37includes=None,38macros=None,39samefiles=None,40filename=None,41cwd=None,42tool=True,43):44"""...4546CWD should be the project root and "source" should be relative.47"""48if tool:49if not cwd:50cwd = os.getcwd()51logger.debug(f'CWD: {cwd!r}')52logger.debug(f'incldirs: {incldirs!r}')53logger.debug(f'includes: {includes!r}')54logger.debug(f'macros: {macros!r}')55logger.debug(f'samefiles: {samefiles!r}')56_preprocess = _get_preprocessor(tool)57with _good_file(source, filename) as source:58return _preprocess(59source,60incldirs,61includes,62macros,63samefiles,64cwd,65) or ()66else:67source, filename = _resolve_source(source, filename)68# We ignore "includes", "macros", etc.69return _pure.preprocess(source, filename, cwd)7071# if _run() returns just the lines:72# text = _run(source)73# lines = [line + os.linesep for line in text.splitlines()]74# lines[-1] = lines[-1].splitlines()[0]75#76# conditions = None77# for lno, line in enumerate(lines, 1):78# kind = 'source'79# directive = None80# data = line81# yield lno, kind, data, conditions828384def get_preprocessor(*,85file_macros=None,86file_includes=None,87file_incldirs=None,88file_same=None,89ignore_exc=False,90log_err=None,91):92_preprocess = preprocess93if file_macros:94file_macros = tuple(_parse_macros(file_macros))95if file_includes:96file_includes = tuple(_parse_includes(file_includes))97if file_incldirs:98file_incldirs = tuple(_parse_incldirs(file_incldirs))99if file_same:100file_same = dict(file_same or ())101if not callable(ignore_exc):102ignore_exc = (lambda exc, _ig=ignore_exc: _ig)103104def get_file_preprocessor(filename):105filename = filename.strip()106if file_macros:107macros = list(_resolve_file_values(filename, file_macros))108if file_includes:109# There's a small chance we could need to filter out any110# includes that import "filename". It isn't clear that it's111# a problem any longer. If we do end up filtering then112# it may make sense to use c_common.fsutil.match_path_tail().113includes = [i for i, in _resolve_file_values(filename, file_includes)]114if file_incldirs:115incldirs = [v for v, in _resolve_file_values(filename, file_incldirs)]116if file_same:117samefiles = _resolve_samefiles(filename, file_same)118119def preprocess(**kwargs):120if file_macros and 'macros' not in kwargs:121kwargs['macros'] = macros122if file_includes and 'includes' not in kwargs:123kwargs['includes'] = includes124if file_incldirs and 'incldirs' not in kwargs:125kwargs['incldirs'] = incldirs126if file_same and 'samefiles' not in kwargs:127kwargs['samefiles'] = samefiles128kwargs.setdefault('filename', filename)129with handling_errors(ignore_exc, log_err=log_err):130return _preprocess(filename, **kwargs)131return preprocess132return get_file_preprocessor133134135def _resolve_file_values(filename, file_values):136# We expect the filename and all patterns to be absolute paths.137for pattern, *value in file_values or ():138if _match_glob(filename, pattern):139yield value140141142def _parse_macros(macros):143for row, srcfile in _parse_table(macros, '\t', 'glob\tname\tvalue', rawsep='=', default=None):144yield row145146147def _parse_includes(includes):148for row, srcfile in _parse_table(includes, '\t', 'glob\tinclude', default=None):149yield row150151152def _parse_incldirs(incldirs):153for row, srcfile in _parse_table(incldirs, '\t', 'glob\tdirname', default=None):154glob, dirname = row155if dirname is None:156# Match all files.157dirname = glob158row = ('*', dirname.strip())159yield row160161162def _resolve_samefiles(filename, file_same):163assert '*' not in filename, (filename,)164assert os.path.normpath(filename) == filename, (filename,)165_, suffix = os.path.splitext(filename)166samefiles = []167for patterns, in _resolve_file_values(filename, file_same.items()):168for pattern in patterns:169same = _resolve_samefile(filename, pattern, suffix)170if not same:171continue172samefiles.append(same)173return samefiles174175176def _resolve_samefile(filename, pattern, suffix):177if pattern == filename:178return None179if pattern.endswith(os.path.sep):180pattern += f'*{suffix}'181assert os.path.normpath(pattern) == pattern, (pattern,)182if '*' in os.path.dirname(pattern):183raise NotImplementedError((filename, pattern))184if '*' not in os.path.basename(pattern):185return pattern186187common = os.path.commonpath([filename, pattern])188relpattern = pattern[len(common) + len(os.path.sep):]189relpatterndir = os.path.dirname(relpattern)190relfile = filename[len(common) + len(os.path.sep):]191if os.path.basename(pattern) == '*':192return os.path.join(common, relpatterndir, relfile)193elif os.path.basename(relpattern) == '*' + suffix:194return os.path.join(common, relpatterndir, relfile)195else:196raise NotImplementedError((filename, pattern))197198199@contextlib.contextmanager200def handling_errors(ignore_exc=None, *, log_err=None):201try:202yield203except _errors.OSMismatchError as exc:204if not ignore_exc(exc):205raise # re-raise206if log_err is not None:207log_err(f'<OS mismatch (expected {" or ".join(exc.expected)})>')208return None209except _errors.MissingDependenciesError as exc:210if not ignore_exc(exc):211raise # re-raise212if log_err is not None:213log_err(f'<missing dependency {exc.missing}')214return None215except _errors.ErrorDirectiveError as exc:216if not ignore_exc(exc):217raise # re-raise218if log_err is not None:219log_err(exc)220return None221222223##################################224# tools225226_COMPILERS = {227# matching distutils.ccompiler.compiler_class:228'unix': _gcc.preprocess,229'msvc': None,230'cygwin': None,231'mingw32': None,232'bcpp': None,233# aliases/extras:234'gcc': _gcc.preprocess,235'clang': None,236}237238239def _get_default_compiler():240if re.match('cygwin.*', sys.platform) is not None:241return 'unix'242if os.name == 'nt':243return 'msvc'244return 'unix'245246247def _get_preprocessor(tool):248if tool is True:249tool = _get_default_compiler()250preprocess = _COMPILERS.get(tool)251if preprocess is None:252raise ValueError(f'unsupported tool {tool}')253return preprocess254255256##################################257# aliases258259from .errors import (260PreprocessorError,261PreprocessorFailure,262ErrorDirectiveError,263MissingDependenciesError,264OSMismatchError,265)266from .common import FileInfo, SourceLine267268269