mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	
		
			
	
	
		
			174 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			174 lines
		
	
	
	
		
			4.6 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, **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) | ||
|  | 
 | ||
|  | 
 | ||
|  | def _build_argv( | ||
|  |     tool, | ||
|  |     filename, | ||
|  |     incldirs=None, | ||
|  |     macros=None, | ||
|  |     preargs=None, | ||
|  |     postargs=None, | ||
|  |     executable=None, | ||
|  |     compiler=None, | ||
|  | ): | ||
|  |     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.debug(stderr.strip()) | ||
|  |         raise OSMismatchError(filename, _expected, argv, error, tool) | ||
|  |     elif (_missing := is_missing_dep(stderr)): | ||
|  |         logger.debug(stderr.strip()) | ||
|  |         raise MissingDependenciesError(filename, (_missing,), argv, error, tool) | ||
|  |     elif '#error' in stderr: | ||
|  |         # XXX Ignore incompatible files. | ||
|  |         error = (stderr.splitlines()[1], rc) | ||
|  |         logger.debug(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 |