PageRenderTime 54ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/clang/tools/scan-build-py/libscanbuild/compilation.py

https://gitlab.com/williamwp/riscv-rv32x-llvm
Python | 140 lines | 92 code | 12 blank | 36 comment | 13 complexity | d90bc7d2b33f378027dc6124d487ab94 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  3. # See https://llvm.org/LICENSE.txt for license information.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. """ This module is responsible for to parse a compiler invocation. """
  6. import re
  7. import os
  8. import collections
  9. __all__ = ['split_command', 'classify_source', 'compiler_language']
  10. # Ignored compiler options map for compilation database creation.
  11. # The map is used in `split_command` method. (Which does ignore and classify
  12. # parameters.) Please note, that these are not the only parameters which
  13. # might be ignored.
  14. #
  15. # Keys are the option name, value number of options to skip
  16. IGNORED_FLAGS = {
  17. # compiling only flag, ignored because the creator of compilation
  18. # database will explicitly set it.
  19. '-c': 0,
  20. # preprocessor macros, ignored because would cause duplicate entries in
  21. # the output (the only difference would be these flags). this is actual
  22. # finding from users, who suffered longer execution time caused by the
  23. # duplicates.
  24. '-MD': 0,
  25. '-MMD': 0,
  26. '-MG': 0,
  27. '-MP': 0,
  28. '-MF': 1,
  29. '-MT': 1,
  30. '-MQ': 1,
  31. # linker options, ignored because for compilation database will contain
  32. # compilation commands only. so, the compiler would ignore these flags
  33. # anyway. the benefit to get rid of them is to make the output more
  34. # readable.
  35. '-static': 0,
  36. '-shared': 0,
  37. '-s': 0,
  38. '-rdynamic': 0,
  39. '-l': 1,
  40. '-L': 1,
  41. '-u': 1,
  42. '-z': 1,
  43. '-T': 1,
  44. '-Xlinker': 1
  45. }
  46. # Known C/C++ compiler executable name patterns
  47. COMPILER_PATTERNS = frozenset([
  48. re.compile(r'^(intercept-|analyze-|)c(c|\+\+)$'),
  49. re.compile(r'^([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'),
  50. re.compile(r'^([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
  51. re.compile(r'^llvm-g(cc|\+\+)$'),
  52. ])
  53. def split_command(command):
  54. """ Returns a value when the command is a compilation, None otherwise.
  55. The value on success is a named tuple with the following attributes:
  56. files: list of source files
  57. flags: list of compile options
  58. compiler: string value of 'c' or 'c++' """
  59. # the result of this method
  60. result = collections.namedtuple('Compilation',
  61. ['compiler', 'flags', 'files'])
  62. result.compiler = compiler_language(command)
  63. result.flags = []
  64. result.files = []
  65. # quit right now, if the program was not a C/C++ compiler
  66. if not result.compiler:
  67. return None
  68. # iterate on the compile options
  69. args = iter(command[1:])
  70. for arg in args:
  71. # quit when compilation pass is not involved
  72. if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
  73. return None
  74. # ignore some flags
  75. elif arg in IGNORED_FLAGS:
  76. count = IGNORED_FLAGS[arg]
  77. for _ in range(count):
  78. next(args)
  79. elif re.match(r'^-(l|L|Wl,).+', arg):
  80. pass
  81. # some parameters could look like filename, take as compile option
  82. elif arg in {'-D', '-I'}:
  83. result.flags.extend([arg, next(args)])
  84. # parameter which looks source file is taken...
  85. elif re.match(r'^[^-].+', arg) and classify_source(arg):
  86. result.files.append(arg)
  87. # and consider everything else as compile option.
  88. else:
  89. result.flags.append(arg)
  90. # do extra check on number of source files
  91. return result if result.files else None
  92. def classify_source(filename, c_compiler=True):
  93. """ Return the language from file name extension. """
  94. mapping = {
  95. '.c': 'c' if c_compiler else 'c++',
  96. '.i': 'c-cpp-output' if c_compiler else 'c++-cpp-output',
  97. '.ii': 'c++-cpp-output',
  98. '.m': 'objective-c',
  99. '.mi': 'objective-c-cpp-output',
  100. '.mm': 'objective-c++',
  101. '.mii': 'objective-c++-cpp-output',
  102. '.C': 'c++',
  103. '.cc': 'c++',
  104. '.CC': 'c++',
  105. '.cp': 'c++',
  106. '.cpp': 'c++',
  107. '.cxx': 'c++',
  108. '.c++': 'c++',
  109. '.C++': 'c++',
  110. '.txx': 'c++'
  111. }
  112. __, extension = os.path.splitext(os.path.basename(filename))
  113. return mapping.get(extension)
  114. def compiler_language(command):
  115. """ A predicate to decide the command is a compiler call or not.
  116. Returns 'c' or 'c++' when it match. None otherwise. """
  117. cplusplus = re.compile(r'^(.+)(\+\+)(-.+|)$')
  118. if command:
  119. executable = os.path.basename(command[0])
  120. if any(pattern.match(executable) for pattern in COMPILER_PATTERNS):
  121. return 'c++' if cplusplus.match(executable) else 'c'
  122. return None