c_build_helper.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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 get_c_expression_values(
  81. cast_to, printf_format,
  82. expressions,
  83. caller=__name__, file_label='',
  84. header='', include_path=None,
  85. keep_c=False,
  86. ): # pylint: disable=too-many-arguments, too-many-locals
  87. """Generate and run a program to print out numerical values for expressions.
  88. * ``cast_to``: a C type.
  89. * ``printf_format``: a printf format suitable for the type ``cast_to``.
  90. * ``header``: extra code to insert before any function in the generated
  91. C file.
  92. * ``expressions``: a list of C language expressions that have the type
  93. ``cast_to``.
  94. * ``include_path``: a list of directories containing header files.
  95. * ``keep_c``: if true, keep the temporary C file (presumably for debugging
  96. purposes).
  97. Use the C compiler specified by the ``CC`` environment variable, defaulting
  98. to ``cc``. If ``CC`` looks like MSVC, use its command line syntax,
  99. otherwise assume the compiler supports Unix traditional ``-I`` and ``-o``.
  100. Return the list of values of the ``expressions``.
  101. """
  102. if include_path is None:
  103. include_path = []
  104. c_name = None
  105. exe_name = None
  106. obj_name = None
  107. try:
  108. c_file, c_name, exe_name = create_c_file(file_label)
  109. generate_c_file(
  110. c_file, caller, header,
  111. lambda c_file: generate_c_printf_expressions(c_file,
  112. cast_to, printf_format,
  113. expressions)
  114. )
  115. c_file.close()
  116. cc = os.getenv('CC', 'cc')
  117. cmd = [cc]
  118. proc = subprocess.Popen(cmd,
  119. stdout=subprocess.DEVNULL,
  120. stderr=subprocess.PIPE,
  121. universal_newlines=True)
  122. cc_is_msvc = 'Microsoft (R) C/C++' in proc.communicate()[1]
  123. cmd += ['-I' + dir for dir in include_path]
  124. if cc_is_msvc:
  125. # MSVC has deprecated using -o to specify the output file,
  126. # and produces an object file in the working directory by default.
  127. obj_name = exe_name[:-4] + '.obj'
  128. cmd += ['-Fe' + exe_name, '-Fo' + obj_name]
  129. else:
  130. cmd += ['-o' + exe_name]
  131. subprocess.check_call(cmd + [c_name])
  132. if keep_c:
  133. sys.stderr.write('List of {} tests kept at {}\n'
  134. .format(caller, c_name))
  135. else:
  136. os.remove(c_name)
  137. output = subprocess.check_output([exe_name])
  138. return output.decode('ascii').strip().split('\n')
  139. finally:
  140. remove_file_if_exists(exe_name)
  141. remove_file_if_exists(obj_name)