quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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)