mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
	
	
		
			191 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			191 lines
		
	
	
	
		
			5.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import contextlib
							 | 
						||
| 
								 | 
							
								import distutils.ccompiler
							 | 
						||
| 
								 | 
							
								import logging
							 | 
						||
| 
								 | 
							
								import os.path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from c_common.fsutil import match_glob as _match_glob
							 | 
						||
| 
								 | 
							
								from c_common.tables import parse_table as _parse_table
							 | 
						||
| 
								 | 
							
								from ..source import (
							 | 
						||
| 
								 | 
							
								    resolve as _resolve_source,
							 | 
						||
| 
								 | 
							
								    good_file as _good_file,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from . import errors as _errors
							 | 
						||
| 
								 | 
							
								from . import (
							 | 
						||
| 
								 | 
							
								    pure as _pure,
							 | 
						||
| 
								 | 
							
								    gcc as _gcc,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								logger = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Supprted "source":
							 | 
						||
| 
								 | 
							
								#  * filename (string)
							 | 
						||
| 
								 | 
							
								#  * lines (iterable)
							 | 
						||
| 
								 | 
							
								#  * text (string)
							 | 
						||
| 
								 | 
							
								# Supported return values:
							 | 
						||
| 
								 | 
							
								#  * iterator of SourceLine
							 | 
						||
| 
								 | 
							
								#  * sequence of SourceLine
							 | 
						||
| 
								 | 
							
								#  * text (string)
							 | 
						||
| 
								 | 
							
								#  * something that combines all those
							 | 
						||
| 
								 | 
							
								# XXX Add the missing support from above.
							 | 
						||
| 
								 | 
							
								# XXX Add more low-level functions to handle permutations?
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def preprocess(source, *,
							 | 
						||
| 
								 | 
							
								               incldirs=None,
							 | 
						||
| 
								 | 
							
								               macros=None,
							 | 
						||
| 
								 | 
							
								               samefiles=None,
							 | 
						||
| 
								 | 
							
								               filename=None,
							 | 
						||
| 
								 | 
							
								               tool=True,
							 | 
						||
| 
								 | 
							
								               ):
							 | 
						||
| 
								 | 
							
								    """...
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    CWD should be the project root and "source" should be relative.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if tool:
							 | 
						||
| 
								 | 
							
								        logger.debug(f'CWD: {os.getcwd()!r}')
							 | 
						||
| 
								 | 
							
								        logger.debug(f'incldirs: {incldirs!r}')
							 | 
						||
| 
								 | 
							
								        logger.debug(f'macros: {macros!r}')
							 | 
						||
| 
								 | 
							
								        logger.debug(f'samefiles: {samefiles!r}')
							 | 
						||
| 
								 | 
							
								        _preprocess = _get_preprocessor(tool)
							 | 
						||
| 
								 | 
							
								        with _good_file(source, filename) as source:
							 | 
						||
| 
								 | 
							
								            return _preprocess(source, incldirs, macros, samefiles) or ()
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        source, filename = _resolve_source(source, filename)
							 | 
						||
| 
								 | 
							
								        # We ignore "includes", "macros", etc.
							 | 
						||
| 
								 | 
							
								        return _pure.preprocess(source, filename)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # if _run() returns just the lines:
							 | 
						||
| 
								 | 
							
								#    text = _run(source)
							 | 
						||
| 
								 | 
							
								#    lines = [line + os.linesep for line in text.splitlines()]
							 | 
						||
| 
								 | 
							
								#    lines[-1] = lines[-1].splitlines()[0]
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#    conditions = None
							 | 
						||
| 
								 | 
							
								#    for lno, line in enumerate(lines, 1):
							 | 
						||
| 
								 | 
							
								#        kind = 'source'
							 | 
						||
| 
								 | 
							
								#        directive = None
							 | 
						||
| 
								 | 
							
								#        data = line
							 | 
						||
| 
								 | 
							
								#        yield lno, kind, data, conditions
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_preprocessor(*,
							 | 
						||
| 
								 | 
							
								                     file_macros=None,
							 | 
						||
| 
								 | 
							
								                     file_incldirs=None,
							 | 
						||
| 
								 | 
							
								                     file_same=None,
							 | 
						||
| 
								 | 
							
								                     ignore_exc=False,
							 | 
						||
| 
								 | 
							
								                     log_err=None,
							 | 
						||
| 
								 | 
							
								                     ):
							 | 
						||
| 
								 | 
							
								    _preprocess = preprocess
							 | 
						||
| 
								 | 
							
								    if file_macros:
							 | 
						||
| 
								 | 
							
								        file_macros = tuple(_parse_macros(file_macros))
							 | 
						||
| 
								 | 
							
								    if file_incldirs:
							 | 
						||
| 
								 | 
							
								        file_incldirs = tuple(_parse_incldirs(file_incldirs))
							 | 
						||
| 
								 | 
							
								    if file_same:
							 | 
						||
| 
								 | 
							
								        file_same = tuple(file_same)
							 | 
						||
| 
								 | 
							
								    if not callable(ignore_exc):
							 | 
						||
| 
								 | 
							
								        ignore_exc = (lambda exc, _ig=ignore_exc: _ig)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_file_preprocessor(filename):
							 | 
						||
| 
								 | 
							
								        filename = filename.strip()
							 | 
						||
| 
								 | 
							
								        if file_macros:
							 | 
						||
| 
								 | 
							
								            macros = list(_resolve_file_values(filename, file_macros))
							 | 
						||
| 
								 | 
							
								        if file_incldirs:
							 | 
						||
| 
								 | 
							
								            incldirs = [v for v, in _resolve_file_values(filename, file_incldirs)]
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								        def preprocess(**kwargs):
							 | 
						||
| 
								 | 
							
								            if file_macros and 'macros' not in kwargs:
							 | 
						||
| 
								 | 
							
								                kwargs['macros'] = macros
							 | 
						||
| 
								 | 
							
								            if file_incldirs and 'incldirs' not in kwargs:
							 | 
						||
| 
								 | 
							
								                kwargs['incldirs'] = [v for v, in _resolve_file_values(filename, file_incldirs)]
							 | 
						||
| 
								 | 
							
								            if file_same and 'file_same' not in kwargs:
							 | 
						||
| 
								 | 
							
								                kwargs['samefiles'] = file_same
							 | 
						||
| 
								 | 
							
								            kwargs.setdefault('filename', filename)
							 | 
						||
| 
								 | 
							
								            with handling_errors(ignore_exc, log_err=log_err):
							 | 
						||
| 
								 | 
							
								                return _preprocess(filename, **kwargs)
							 | 
						||
| 
								 | 
							
								        return preprocess
							 | 
						||
| 
								 | 
							
								    return get_file_preprocessor
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _resolve_file_values(filename, file_values):
							 | 
						||
| 
								 | 
							
								    # We expect the filename and all patterns to be absolute paths.
							 | 
						||
| 
								 | 
							
								    for pattern, *value in file_values or ():
							 | 
						||
| 
								 | 
							
								        if _match_glob(filename, pattern):
							 | 
						||
| 
								 | 
							
								            yield value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _parse_macros(macros):
							 | 
						||
| 
								 | 
							
								    for row, srcfile in _parse_table(macros, '\t', 'glob\tname\tvalue', rawsep='=', default=None):
							 | 
						||
| 
								 | 
							
								        yield row
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _parse_incldirs(incldirs):
							 | 
						||
| 
								 | 
							
								    for row, srcfile in _parse_table(incldirs, '\t', 'glob\tdirname', default=None):
							 | 
						||
| 
								 | 
							
								        glob, dirname = row
							 | 
						||
| 
								 | 
							
								        if dirname is None:
							 | 
						||
| 
								 | 
							
								            # Match all files.
							 | 
						||
| 
								 | 
							
								            dirname = glob
							 | 
						||
| 
								 | 
							
								            row = ('*', dirname.strip())
							 | 
						||
| 
								 | 
							
								        yield row
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@contextlib.contextmanager
							 | 
						||
| 
								 | 
							
								def handling_errors(ignore_exc=None, *, log_err=None):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        yield
							 | 
						||
| 
								 | 
							
								    except _errors.OSMismatchError as exc:
							 | 
						||
| 
								 | 
							
								        if not ignore_exc(exc):
							 | 
						||
| 
								 | 
							
								            raise  # re-raise
							 | 
						||
| 
								 | 
							
								        if log_err is not None:
							 | 
						||
| 
								 | 
							
								            log_err(f'<OS mismatch (expected {" or ".join(exc.expected)})>')
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    except _errors.MissingDependenciesError as exc:
							 | 
						||
| 
								 | 
							
								        if not ignore_exc(exc):
							 | 
						||
| 
								 | 
							
								            raise  # re-raise
							 | 
						||
| 
								 | 
							
								        if log_err is not None:
							 | 
						||
| 
								 | 
							
								            log_err(f'<missing dependency {exc.missing}')
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    except _errors.ErrorDirectiveError as exc:
							 | 
						||
| 
								 | 
							
								        if not ignore_exc(exc):
							 | 
						||
| 
								 | 
							
								            raise  # re-raise
							 | 
						||
| 
								 | 
							
								        if log_err is not None:
							 | 
						||
| 
								 | 
							
								            log_err(exc)
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								##################################
							 | 
						||
| 
								 | 
							
								# tools
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_COMPILERS = {
							 | 
						||
| 
								 | 
							
								    # matching disutils.ccompiler.compiler_class:
							 | 
						||
| 
								 | 
							
								    'unix': _gcc.preprocess,
							 | 
						||
| 
								 | 
							
								    'msvc': None,
							 | 
						||
| 
								 | 
							
								    'cygwin': None,
							 | 
						||
| 
								 | 
							
								    'mingw32': None,
							 | 
						||
| 
								 | 
							
								    'bcpp': None,
							 | 
						||
| 
								 | 
							
								    # aliases/extras:
							 | 
						||
| 
								 | 
							
								    'gcc': _gcc.preprocess,
							 | 
						||
| 
								 | 
							
								    'clang': None,
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _get_preprocessor(tool):
							 | 
						||
| 
								 | 
							
								    if tool is True:
							 | 
						||
| 
								 | 
							
								        tool = distutils.ccompiler.get_default_compiler()
							 | 
						||
| 
								 | 
							
								    preprocess = _COMPILERS.get(tool)
							 | 
						||
| 
								 | 
							
								    if preprocess is None:
							 | 
						||
| 
								 | 
							
								        raise ValueError(f'unsupported tool {tool}')
							 | 
						||
| 
								 | 
							
								    return preprocess
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								##################################
							 | 
						||
| 
								 | 
							
								# aliases
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .errors import (
							 | 
						||
| 
								 | 
							
								    PreprocessorError,
							 | 
						||
| 
								 | 
							
								    PreprocessorFailure,
							 | 
						||
| 
								 | 
							
								    ErrorDirectiveError,
							 | 
						||
| 
								 | 
							
								    MissingDependenciesError,
							 | 
						||
| 
								 | 
							
								    OSMismatchError,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from .common import FileInfo, SourceLine
							 |