File: c_build_helper.py

package info (click to toggle)
mbedtls 3.6.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 50,424 kB
  • sloc: ansic: 164,526; sh: 25,295; python: 14,825; makefile: 2,761; perl: 1,043; tcl: 4
file content (177 lines) | stat: -rw-r--r-- 6,023 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""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

class CompileError(Exception):
    """Exception to represent an error during the compilation."""

    def __init__(self, message):
        """Save the error massage"""

        super().__init__()
        self.message = message

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 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]

    try:
        subprocess.check_output(cmd + [c_filename],
                                stderr=subprocess.PIPE,
                                universal_newlines=True)

    except subprocess.CalledProcessError as e:
        raise CompileError(e.stderr) from e

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()

        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)
        remove_file_if_exists(obj_name)