| """Generate and run C code. |
| """ |
| |
| # Copyright The Mbed TLS Contributors |
| # SPDX-License-Identifier: Apache-2.0 |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| # not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| 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 <stdio.h> |
| ''') |
| c_file.write(header) |
| c_file.write(''' |
| int main(void) |
| { |
| ''') |
| main_generator(c_file) |
| c_file.write(''' return 0; |
| } |
| ''') |
| |
| 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, too-many-locals |
| """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). |
| |
| Use the C compiler specified by the ``CC`` environment variable, defaulting |
| to ``cc``. If ``CC`` looks like MSVC, use its command line syntax, |
| otherwise assume the compiler supports Unix traditional ``-I`` and ``-o``. |
| |
| Return the list of values of the ``expressions``. |
| """ |
| if include_path is None: |
| include_path = [] |
| c_name = None |
| exe_name = None |
| obj_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() |
| 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++ Optimizing Compiler' in \ |
| proc.communicate()[1] |
| |
| cmd += ['-I' + dir for dir in include_path] |
| 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_name = exe_name[:-4] + '.obj' |
| cmd += ['-Fe' + exe_name, '-Fo' + obj_name] |
| else: |
| cmd += ['-o' + exe_name] |
| subprocess.check_call(cmd + [c_name]) |
| 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) |
| remove_file_if_exists(obj_name) |