c_build_helper.py 6.0 KB

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