cmakeparser.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from pyparsing import (CharsNotIn, Group, Forward, Literal, Suppress, Word,
  5. QuotedString, ZeroOrMore, alphas, alphanums)
  6. from string import Template
  7. import re
  8. # Grammar for CMake
  9. comment = Literal('#') + ZeroOrMore(CharsNotIn('\n'))
  10. quoted_argument = QuotedString('\"', '\\', multiline=True)
  11. unquoted_argument = CharsNotIn('\n ()#\"\\')
  12. argument = quoted_argument | unquoted_argument | Suppress(comment)
  13. arguments = Forward()
  14. arguments << (argument | (Literal('(') + ZeroOrMore(arguments) + Literal(')')))
  15. identifier = Word(alphas, alphanums+'_')
  16. command = Group(identifier + Literal('(') + ZeroOrMore(arguments) + Literal(')'))
  17. file_elements = command | Suppress(comment)
  18. cmake = ZeroOrMore(file_elements)
  19. def extract_arguments(parsed):
  20. """Extract the command arguments skipping the parentheses"""
  21. return parsed[2:len(parsed) - 1]
  22. def match_block(command, parsed, start):
  23. """Find the end of block starting with the command"""
  24. depth = 0
  25. end = start + 1
  26. endcommand = 'end' + command
  27. while parsed[end][0] != endcommand or depth > 0:
  28. if parsed[end][0] == command:
  29. depth += 1
  30. elif parsed[end][0] == endcommand:
  31. depth -= 1
  32. end = end + 1
  33. if end == len(parsed):
  34. print('error: eof when trying to match block statement: %s'
  35. % parsed[start])
  36. return end
  37. def parse_if(parsed, start):
  38. """Parse if/elseif/else/endif into a list of conditions and commands"""
  39. depth = 0
  40. conditions = []
  41. condition = [extract_arguments(parsed[start])]
  42. start = start + 1
  43. end = start
  44. while parsed[end][0] != 'endif' or depth > 0:
  45. command = parsed[end][0]
  46. if command == 'if':
  47. depth += 1
  48. elif command == 'else' and depth == 0:
  49. condition.append(parsed[start:end])
  50. conditions.append(condition)
  51. start = end + 1
  52. condition = [['TRUE']]
  53. elif command == 'elseif' and depth == 0:
  54. condition.append(parsed[start:end])
  55. conditions.append(condition)
  56. condition = [extract_arguments(parsed[end])]
  57. start = end + 1
  58. elif command == 'endif':
  59. depth -= 1
  60. end = end + 1
  61. if end == len(parsed):
  62. print('error: eof when trying to match if statement: %s'
  63. % parsed[start])
  64. condition.append(parsed[start:end])
  65. conditions.append(condition)
  66. return end, conditions
  67. def substs(variables, values):
  68. """Substitute variables into values"""
  69. new_values = []
  70. for value in values:
  71. t = Template(value)
  72. new_value = t.safe_substitute(variables)
  73. # Safe substitute leaves unrecognized variables in place.
  74. # We replace them with the empty string.
  75. new_values.append(re.sub('\$\{\w+\}', '', new_value))
  76. return new_values
  77. def evaluate(variables, cache_variables, parsed):
  78. """Evaluate a list of parsed commands, returning sources to build"""
  79. i = 0
  80. sources = []
  81. while i < len(parsed):
  82. command = parsed[i][0]
  83. arguments = substs(variables, extract_arguments(parsed[i]))
  84. if command == 'foreach':
  85. end = match_block(command, parsed, i)
  86. for argument in arguments[1:]:
  87. # ; is also a valid divider, why have one when you can have two?
  88. argument = argument.replace(';', ' ')
  89. for value in argument.split():
  90. variables[arguments[0]] = value
  91. cont_eval, new_sources = evaluate(variables, cache_variables,
  92. parsed[i+1:end])
  93. sources.extend(new_sources)
  94. if not cont_eval:
  95. return cont_eval, sources
  96. elif command == 'function':
  97. # for now we just execute functions inline at point of declaration
  98. # as this is sufficient to build libaom
  99. pass
  100. elif command == 'if':
  101. i, conditions = parse_if(parsed, i)
  102. for condition in conditions:
  103. if evaluate_boolean(variables, condition[0]):
  104. cont_eval, new_sources = evaluate(variables,
  105. cache_variables,
  106. condition[1])
  107. sources.extend(new_sources)
  108. if not cont_eval:
  109. return cont_eval, sources
  110. break
  111. elif command == 'include':
  112. if arguments:
  113. try:
  114. print('including: %s' % arguments[0])
  115. sources.extend(parse(variables, cache_variables, arguments[0]))
  116. except IOError:
  117. print('warning: could not include: %s' % arguments[0])
  118. elif command == 'list':
  119. try:
  120. action = arguments[0]
  121. variable = arguments[1]
  122. values = arguments[2:]
  123. if action == 'APPEND':
  124. if not variables.has_key(variable):
  125. variables[variable] = ' '.join(values)
  126. else:
  127. variables[variable] += ' ' + ' '.join(values)
  128. except (IndexError, KeyError):
  129. pass
  130. elif command == 'option':
  131. variable = arguments[0]
  132. value = arguments[2]
  133. # Allow options to be override without changing CMake files
  134. if not variables.has_key(variable):
  135. variables[variable] = value
  136. elif command == 'return':
  137. return False, sources
  138. elif command == 'set':
  139. variable = arguments[0]
  140. values = arguments[1:]
  141. # CACHE variables are not set if already present
  142. try:
  143. cache = values.index('CACHE')
  144. values = values[0:cache]
  145. if not variables.has_key(variable):
  146. variables[variable] = ' '.join(values)
  147. cache_variables.append(variable)
  148. except ValueError:
  149. variables[variable] = ' '.join(values)
  150. # we need to emulate the behavior of these function calls
  151. # because we don't support interpreting them directly
  152. # see bug 1492292
  153. elif command in ['set_aom_config_var', 'set_aom_detect_var']:
  154. variable = arguments[0]
  155. value = arguments[1]
  156. if variable not in variables:
  157. variables[variable] = value
  158. cache_variables.append(variable)
  159. elif command == 'set_aom_option_var':
  160. # option vars cannot go into cache_variables
  161. variable = arguments[0]
  162. value = arguments[2]
  163. if variable not in variables:
  164. variables[variable] = value
  165. elif command == 'add_asm_library':
  166. try:
  167. sources.extend(variables[arguments[1]].split(' '))
  168. except (IndexError, KeyError):
  169. pass
  170. elif command == 'add_intrinsics_object_library':
  171. try:
  172. sources.extend(variables[arguments[3]].split(' '))
  173. except (IndexError, KeyError):
  174. pass
  175. elif command == 'add_library':
  176. for source in arguments[1:]:
  177. sources.extend(source.split(' '))
  178. elif command == 'target_sources':
  179. for source in arguments[1:]:
  180. sources.extend(source.split(' '))
  181. elif command == 'MOZDEBUG':
  182. print('>>>> MOZDEBUG: %s' % ' '.join(arguments))
  183. i += 1
  184. return True, sources
  185. def evaluate_boolean(variables, arguments):
  186. """Evaluate a boolean expression"""
  187. if not arguments:
  188. return False
  189. argument = arguments[0]
  190. if argument == 'NOT':
  191. return not evaluate_boolean(variables, arguments[1:])
  192. if argument == '(':
  193. i = 0
  194. depth = 1
  195. while depth > 0 and i < len(arguments):
  196. i += 1
  197. if arguments[i] == '(':
  198. depth += 1
  199. if arguments[i] == ')':
  200. depth -= 1
  201. return evaluate_boolean(variables, arguments[1:i])
  202. def evaluate_constant(argument):
  203. try:
  204. as_int = int(argument)
  205. if as_int != 0:
  206. return True
  207. else:
  208. return False
  209. except ValueError:
  210. upper = argument.upper()
  211. if upper in ['ON', 'YES', 'TRUE', 'Y']:
  212. return True
  213. elif upper in ['OFF', 'NO', 'FALSE', 'N', 'IGNORE', '', 'NOTFOUND']:
  214. return False
  215. elif upper.endswith('-NOTFOUND'):
  216. return False
  217. return None
  218. def lookup_variable(argument):
  219. # If statements can have old-style variables which are not demarcated
  220. # like ${VARIABLE}. Attempt to look up the variable both ways.
  221. try:
  222. if re.search('\$\{\w+\}', argument):
  223. try:
  224. t = Template(argument)
  225. value = t.substitute(variables)
  226. try:
  227. # Attempt an old-style variable lookup with the
  228. # substituted value.
  229. return variables[value]
  230. except KeyError:
  231. return value
  232. except ValueError:
  233. # TODO: CMake supports nesting, e.g. ${${foo}}
  234. return None
  235. else:
  236. return variables[argument]
  237. except KeyError:
  238. return None
  239. lhs = lookup_variable(argument)
  240. if lhs is None:
  241. # variable resolution failed, treat as string
  242. lhs = argument
  243. if len(arguments) > 1:
  244. op = arguments[1]
  245. if op == 'AND':
  246. return evaluate_constant(lhs) and evaluate_boolean(variables, arguments[2:])
  247. elif op == 'MATCHES':
  248. rhs = lookup_variable(arguments[2])
  249. if not rhs:
  250. rhs = arguments[2]
  251. return not re.match(rhs, lhs) is None
  252. elif op == 'OR':
  253. return evaluate_constant(lhs) or evaluate_boolean(variables, arguments[2:])
  254. elif op == 'STREQUAL':
  255. rhs = lookup_variable(arguments[2])
  256. if not rhs:
  257. rhs = arguments[2]
  258. return lhs == rhs
  259. else:
  260. lhs = evaluate_constant(lhs)
  261. if lhs is None:
  262. lhs = lookup_variable(argument)
  263. return lhs
  264. def parse(variables, cache_variables, filename):
  265. parsed = cmake.parseFile(filename)
  266. cont_eval, sources = evaluate(variables, cache_variables, parsed)
  267. return sources