mirror of
				https://github.com/python/cpython.git
				synced 2025-10-25 10:44:55 +00:00 
			
		
		
		
	 1ff81c0cb6
			
		
	
	
		1ff81c0cb6
		
			
		
	
	
	
	
		
			
			This will keep us from adding new unsupported (i.e. non-const) C global variables, which would break interpreter isolation. FYI, historically it is very uncommon for new global variables to get added. Furthermore, it is rare for new code to break the c-analyzer. So the check should almost always pass unnoticed. Note that I've removed test_check_c_globals. A test wasn't a great fit conceptually and was super slow on debug builds. A CI check is a better fit. This also resolves gh-100237. https://github.com/python/cpython/issues/81057
		
			
				
	
	
		
			178 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import contextlib
 | |
| import distutils.ccompiler
 | |
| import logging
 | |
| import shlex
 | |
| import subprocess
 | |
| import sys
 | |
| 
 | |
| from ..info import FileInfo, SourceLine
 | |
| from .errors import (
 | |
|     PreprocessorFailure,
 | |
|     ErrorDirectiveError,
 | |
|     MissingDependenciesError,
 | |
|     OSMismatchError,
 | |
| )
 | |
| 
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| # XXX Add aggregate "source" class(es)?
 | |
| #  * expose all lines as single text string
 | |
| #  * expose all lines as sequence
 | |
| #  * iterate all lines
 | |
| 
 | |
| 
 | |
| def run_cmd(argv, *,
 | |
|             #capture_output=True,
 | |
|             stdout=subprocess.PIPE,
 | |
|             #stderr=subprocess.STDOUT,
 | |
|             stderr=subprocess.PIPE,
 | |
|             text=True,
 | |
|             check=True,
 | |
|             **kwargs
 | |
|             ):
 | |
|     if isinstance(stderr, str) and stderr.lower() == 'stdout':
 | |
|         stderr = subprocess.STDOUT
 | |
| 
 | |
|     kw = dict(locals())
 | |
|     kw.pop('argv')
 | |
|     kw.pop('kwargs')
 | |
|     kwargs.update(kw)
 | |
| 
 | |
|     proc = subprocess.run(argv, **kwargs)
 | |
|     return proc.stdout
 | |
| 
 | |
| 
 | |
| def preprocess(tool, filename, cwd=None, **kwargs):
 | |
|     argv = _build_argv(tool, filename, **kwargs)
 | |
|     logger.debug(' '.join(shlex.quote(v) for v in argv))
 | |
| 
 | |
|     # Make sure the OS is supported for this file.
 | |
|     if (_expected := is_os_mismatch(filename)):
 | |
|         error = None
 | |
|         raise OSMismatchError(filename, _expected, argv, error, TOOL)
 | |
| 
 | |
|     # Run the command.
 | |
|     with converted_error(tool, argv, filename):
 | |
|         # We use subprocess directly here, instead of calling the
 | |
|         # distutil compiler object's preprocess() method, since that
 | |
|         # one writes to stdout/stderr and it's simpler to do it directly
 | |
|         # through subprocess.
 | |
|         return run_cmd(argv, cwd=cwd)
 | |
| 
 | |
| 
 | |
| def _build_argv(
 | |
|     tool,
 | |
|     filename,
 | |
|     incldirs=None,
 | |
|     includes=None,
 | |
|     macros=None,
 | |
|     preargs=None,
 | |
|     postargs=None,
 | |
|     executable=None,
 | |
|     compiler=None,
 | |
| ):
 | |
|     if includes:
 | |
|         includes = tuple(f'-include{i}' for i in includes)
 | |
|         postargs = (includes + postargs) if postargs else includes
 | |
| 
 | |
|     compiler = distutils.ccompiler.new_compiler(
 | |
|         compiler=compiler or tool,
 | |
|     )
 | |
|     if executable:
 | |
|         compiler.set_executable('preprocessor', executable)
 | |
| 
 | |
|     argv = None
 | |
|     def _spawn(_argv):
 | |
|         nonlocal argv
 | |
|         argv = _argv
 | |
|     compiler.spawn = _spawn
 | |
|     compiler.preprocess(
 | |
|         filename,
 | |
|         macros=[tuple(v) for v in macros or ()],
 | |
|         include_dirs=incldirs or (),
 | |
|         extra_preargs=preargs or (),
 | |
|         extra_postargs=postargs or (),
 | |
|     )
 | |
|     return argv
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def converted_error(tool, argv, filename):
 | |
|     try:
 | |
|         yield
 | |
|     except subprocess.CalledProcessError as exc:
 | |
|         convert_error(
 | |
|             tool,
 | |
|             argv,
 | |
|             filename,
 | |
|             exc.stderr,
 | |
|             exc.returncode,
 | |
|         )
 | |
| 
 | |
| 
 | |
| def convert_error(tool, argv, filename, stderr, rc):
 | |
|     error = (stderr.splitlines()[0], rc)
 | |
|     if (_expected := is_os_mismatch(filename, stderr)):
 | |
|         logger.info(stderr.strip())
 | |
|         raise OSMismatchError(filename, _expected, argv, error, tool)
 | |
|     elif (_missing := is_missing_dep(stderr)):
 | |
|         logger.info(stderr.strip())
 | |
|         raise MissingDependenciesError(filename, (_missing,), argv, error, tool)
 | |
|     elif '#error' in stderr:
 | |
|         # XXX Ignore incompatible files.
 | |
|         error = (stderr.splitlines()[1], rc)
 | |
|         logger.info(stderr.strip())
 | |
|         raise ErrorDirectiveError(filename, argv, error, tool)
 | |
|     else:
 | |
|         # Try one more time, with stderr written to the terminal.
 | |
|         try:
 | |
|             output = run_cmd(argv, stderr=None)
 | |
|         except subprocess.CalledProcessError:
 | |
|             raise PreprocessorFailure(filename, argv, error, tool)
 | |
| 
 | |
| 
 | |
| def is_os_mismatch(filename, errtext=None):
 | |
|     # See: https://docs.python.org/3/library/sys.html#sys.platform
 | |
|     actual = sys.platform
 | |
|     if actual == 'unknown':
 | |
|         raise NotImplementedError
 | |
| 
 | |
|     if errtext is not None:
 | |
|         if (missing := is_missing_dep(errtext)):
 | |
|             matching = get_matching_oses(missing, filename)
 | |
|             if actual not in matching:
 | |
|                 return matching
 | |
|     return False
 | |
| 
 | |
| 
 | |
| def get_matching_oses(missing, filename):
 | |
|     # OSX
 | |
|     if 'darwin' in filename or 'osx' in filename:
 | |
|         return ('darwin',)
 | |
|     elif missing == 'SystemConfiguration/SystemConfiguration.h':
 | |
|         return ('darwin',)
 | |
| 
 | |
|     # Windows
 | |
|     elif missing in ('windows.h', 'winsock2.h'):
 | |
|         return ('win32',)
 | |
| 
 | |
|     # other
 | |
|     elif missing == 'sys/ldr.h':
 | |
|         return ('aix',)
 | |
|     elif missing == 'dl.h':
 | |
|         # XXX The existence of Python/dynload_dl.c implies others...
 | |
|         # Note that hpux isn't actual supported any more.
 | |
|         return ('hpux', '???')
 | |
| 
 | |
|     # unrecognized
 | |
|     else:
 | |
|         return ()
 | |
| 
 | |
| 
 | |
| def is_missing_dep(errtext):
 | |
|     if 'No such file or directory' in errtext:
 | |
|         missing = errtext.split(': No such file or directory')[0].split()[-1]
 | |
|         return missing
 | |
|     return False
 |