"""Generate and run C code. """ # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later # import os import platform import subprocess import sys import tempfile def remove_file_if_exists(filename): """Remove the specified file, ignoring errors.""" if not filename: return try: os.remove(filename) except OSError: pass def create_c_file(file_label): """Create a temporary C file. * ``file_label``: a string that will be included in the file name. Return ```(c_file, c_name, exe_name)``` where ``c_file`` is a Python stream open for writing to the file, ``c_name`` is the name of the file and ``exe_name`` is the name of the executable that will be produced by compiling the file. """ c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(file_label), suffix='.c') exe_suffix = '.exe' if platform.system() == 'Windows' else '' exe_name = c_name[:-2] + exe_suffix remove_file_if_exists(exe_name) c_file = os.fdopen(c_fd, 'w', encoding='ascii') return c_file, c_name, exe_name def generate_c_printf_expressions(c_file, cast_to, printf_format, expressions): """Generate C instructions to print the value of ``expressions``. Write the code with ``c_file``'s ``write`` method. Each expression is cast to the type ``cast_to`` and printed with the printf format ``printf_format``. """ for expr in expressions: c_file.write(' printf("{}\\n", ({}) {});\n' .format(printf_format, cast_to, expr)) def generate_c_file(c_file, caller, header, main_generator): """Generate a temporary C source file. * ``c_file`` is an open stream on the C source file. * ``caller``: an informational string written in a comment at the top of the file. * ``header``: extra code to insert before any function in the generated C file. * ``main_generator``: a function called with ``c_file`` as its sole argument to generate the body of the ``main()`` function. """ c_file.write('/* Generated by {} */' .format(caller)) c_file.write(''' #include ''') c_file.write(header) c_file.write(''' int main(void) { ''') main_generator(c_file) c_file.write(''' return 0; } ''') def compile_c_file(c_filename, exe_filename, include_dirs): """Compile a C source file with the host compiler. * ``c_filename``: the name of the source file to compile. * ``exe_filename``: the name for the executable to be created. * ``include_dirs``: a list of paths to include directories to be passed with the -I switch. """ # Respect $HOSTCC if it is set cc = os.getenv('HOSTCC', None) if cc is None: cc = os.getenv('CC', 'cc') cmd = [cc] proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True) cc_is_msvc = 'Microsoft (R) C/C++' in proc.communicate()[1] cmd += ['-I' + dir for dir in include_dirs] if cc_is_msvc: # MSVC has deprecated using -o to specify the output file, # and produces an object file in the working directory by default. obj_filename = exe_filename[:-4] + '.obj' cmd += ['-Fe' + exe_filename, '-Fo' + obj_filename] else: cmd += ['-o' + exe_filename] subprocess.check_call(cmd + [c_filename]) def get_c_expression_values( cast_to, printf_format, expressions, caller=__name__, file_label='', header='', include_path=None, keep_c=False, ): # pylint: disable=too-many-arguments """Generate and run a program to print out numerical values for expressions. * ``cast_to``: a C type. * ``printf_format``: a printf format suitable for the type ``cast_to``. * ``header``: extra code to insert before any function in the generated C file. * ``expressions``: a list of C language expressions that have the type ``cast_to``. * ``include_path``: a list of directories containing header files. * ``keep_c``: if true, keep the temporary C file (presumably for debugging purposes). Return the list of values of the ``expressions``. """ if include_path is None: include_path = [] c_name = None exe_name = None try: c_file, c_name, exe_name = create_c_file(file_label) generate_c_file( c_file, caller, header, lambda c_file: generate_c_printf_expressions(c_file, cast_to, printf_format, expressions) ) c_file.close() compile_c_file(c_name, exe_name, include_path) if keep_c: sys.stderr.write('List of {} tests kept at {}\n' .format(caller, c_name)) else: os.remove(c_name) output = subprocess.check_output([exe_name]) return output.decode('ascii').strip().split('\n') finally: remove_file_if_exists(exe_name)