c_build_helper.py (6023B)
1 """Generate and run C code. 2 """ 3 4 # Copyright The Mbed TLS Contributors 5 # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 6 # 7 8 import os 9 import platform 10 import subprocess 11 import sys 12 import tempfile 13 14 class CompileError(Exception): 15 """Exception to represent an error during the compilation.""" 16 17 def __init__(self, message): 18 """Save the error massage""" 19 20 super().__init__() 21 self.message = message 22 23 def remove_file_if_exists(filename): 24 """Remove the specified file, ignoring errors.""" 25 if not filename: 26 return 27 try: 28 os.remove(filename) 29 except OSError: 30 pass 31 32 def create_c_file(file_label): 33 """Create a temporary C file. 34 35 * ``file_label``: a string that will be included in the file name. 36 37 Return ```(c_file, c_name, exe_name)``` where ``c_file`` is a Python 38 stream open for writing to the file, ``c_name`` is the name of the file 39 and ``exe_name`` is the name of the executable that will be produced 40 by compiling the file. 41 """ 42 c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(file_label), 43 suffix='.c') 44 exe_suffix = '.exe' if platform.system() == 'Windows' else '' 45 exe_name = c_name[:-2] + exe_suffix 46 remove_file_if_exists(exe_name) 47 c_file = os.fdopen(c_fd, 'w', encoding='ascii') 48 return c_file, c_name, exe_name 49 50 def generate_c_printf_expressions(c_file, cast_to, printf_format, expressions): 51 """Generate C instructions to print the value of ``expressions``. 52 53 Write the code with ``c_file``'s ``write`` method. 54 55 Each expression is cast to the type ``cast_to`` and printed with the 56 printf format ``printf_format``. 57 """ 58 for expr in expressions: 59 c_file.write(' printf("{}\\n", ({}) {});\n' 60 .format(printf_format, cast_to, expr)) 61 62 def generate_c_file(c_file, 63 caller, header, 64 main_generator): 65 """Generate a temporary C source file. 66 67 * ``c_file`` is an open stream on the C source file. 68 * ``caller``: an informational string written in a comment at the top 69 of the file. 70 * ``header``: extra code to insert before any function in the generated 71 C file. 72 * ``main_generator``: a function called with ``c_file`` as its sole argument 73 to generate the body of the ``main()`` function. 74 """ 75 c_file.write('/* Generated by {} */' 76 .format(caller)) 77 c_file.write(''' 78 #include <stdio.h> 79 ''') 80 c_file.write(header) 81 c_file.write(''' 82 int main(void) 83 { 84 ''') 85 main_generator(c_file) 86 c_file.write(''' return 0; 87 } 88 ''') 89 90 def compile_c_file(c_filename, exe_filename, include_dirs): 91 """Compile a C source file with the host compiler. 92 93 * ``c_filename``: the name of the source file to compile. 94 * ``exe_filename``: the name for the executable to be created. 95 * ``include_dirs``: a list of paths to include directories to be passed 96 with the -I switch. 97 """ 98 # Respect $HOSTCC if it is set 99 cc = os.getenv('HOSTCC', None) 100 if cc is None: 101 cc = os.getenv('CC', 'cc') 102 cmd = [cc] 103 104 proc = subprocess.Popen(cmd, 105 stdout=subprocess.DEVNULL, 106 stderr=subprocess.PIPE, 107 universal_newlines=True) 108 cc_is_msvc = 'Microsoft (R) C/C++' in proc.communicate()[1] 109 110 cmd += ['-I' + dir for dir in include_dirs] 111 if cc_is_msvc: 112 # MSVC has deprecated using -o to specify the output file, 113 # and produces an object file in the working directory by default. 114 obj_filename = exe_filename[:-4] + '.obj' 115 cmd += ['-Fe' + exe_filename, '-Fo' + obj_filename] 116 else: 117 cmd += ['-o' + exe_filename] 118 119 try: 120 subprocess.check_output(cmd + [c_filename], 121 stderr=subprocess.PIPE, 122 universal_newlines=True) 123 124 except subprocess.CalledProcessError as e: 125 raise CompileError(e.stderr) from e 126 127 def get_c_expression_values( 128 cast_to, printf_format, 129 expressions, 130 caller=__name__, file_label='', 131 header='', include_path=None, 132 keep_c=False, 133 ): # pylint: disable=too-many-arguments, too-many-locals 134 """Generate and run a program to print out numerical values for expressions. 135 136 * ``cast_to``: a C type. 137 * ``printf_format``: a printf format suitable for the type ``cast_to``. 138 * ``header``: extra code to insert before any function in the generated 139 C file. 140 * ``expressions``: a list of C language expressions that have the type 141 ``cast_to``. 142 * ``include_path``: a list of directories containing header files. 143 * ``keep_c``: if true, keep the temporary C file (presumably for debugging 144 purposes). 145 146 Use the C compiler specified by the ``CC`` environment variable, defaulting 147 to ``cc``. If ``CC`` looks like MSVC, use its command line syntax, 148 otherwise assume the compiler supports Unix traditional ``-I`` and ``-o``. 149 150 Return the list of values of the ``expressions``. 151 """ 152 if include_path is None: 153 include_path = [] 154 c_name = None 155 exe_name = None 156 obj_name = None 157 try: 158 c_file, c_name, exe_name = create_c_file(file_label) 159 generate_c_file( 160 c_file, caller, header, 161 lambda c_file: generate_c_printf_expressions(c_file, 162 cast_to, printf_format, 163 expressions) 164 ) 165 c_file.close() 166 167 compile_c_file(c_name, exe_name, include_path) 168 if keep_c: 169 sys.stderr.write('List of {} tests kept at {}\n' 170 .format(caller, c_name)) 171 else: 172 os.remove(c_name) 173 output = subprocess.check_output([exe_name]) 174 return output.decode('ascii').strip().split('\n') 175 finally: 176 remove_file_if_exists(exe_name) 177 remove_file_if_exists(obj_name)