astinterpreter.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # Copyright 2016 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. # This class contains the basic functionality needed to run any interpreter
  12. # or an interpreter-based tool.
  13. from . import interpreterbase, mlog, mparser, mesonlib
  14. from . import environment
  15. from .interpreterbase import InterpreterException, InvalidArguments
  16. import os, sys
  17. class DontCareObject(interpreterbase.InterpreterObject):
  18. pass
  19. class MockExecutable(interpreterbase.InterpreterObject):
  20. pass
  21. class MockStaticLibrary(interpreterbase.InterpreterObject):
  22. pass
  23. class MockSharedLibrary(interpreterbase.InterpreterObject):
  24. pass
  25. class MockCustomTarget(interpreterbase.InterpreterObject):
  26. pass
  27. class MockRunTarget(interpreterbase.InterpreterObject):
  28. pass
  29. ADD_SOURCE = 0
  30. REMOVE_SOURCE = 1
  31. class AstInterpreter(interpreterbase.InterpreterBase):
  32. def __init__(self, source_root, subdir):
  33. super().__init__(source_root, subdir)
  34. self.asts = {}
  35. self.funcs.update({'project': self.func_do_nothing,
  36. 'test': self.func_do_nothing,
  37. 'benchmark': self.func_do_nothing,
  38. 'install_headers': self.func_do_nothing,
  39. 'install_man': self.func_do_nothing,
  40. 'install_data': self.func_do_nothing,
  41. 'install_subdir': self.func_do_nothing,
  42. 'configuration_data': self.func_do_nothing,
  43. 'configure_file': self.func_do_nothing,
  44. 'find_program': self.func_do_nothing,
  45. 'include_directories': self.func_do_nothing,
  46. 'add_global_arguments': self.func_do_nothing,
  47. 'add_global_link_arguments': self.func_do_nothing,
  48. 'add_project_arguments': self.func_do_nothing,
  49. 'add_project_link_arguments': self.func_do_nothing,
  50. 'message': self.func_do_nothing,
  51. 'generator': self.func_do_nothing,
  52. 'error': self.func_do_nothing,
  53. 'run_command': self.func_do_nothing,
  54. 'assert': self.func_do_nothing,
  55. 'subproject': self.func_do_nothing,
  56. 'dependency': self.func_do_nothing,
  57. 'get_option': self.func_do_nothing,
  58. 'join_paths': self.func_do_nothing,
  59. 'environment': self.func_do_nothing,
  60. 'import': self.func_do_nothing,
  61. 'vcs_tag': self.func_do_nothing,
  62. 'add_languages': self.func_do_nothing,
  63. 'declare_dependency': self.func_do_nothing,
  64. 'files': self.func_files,
  65. 'executable': self.func_executable,
  66. 'static_library': self.func_static_lib,
  67. 'shared_library': self.func_shared_lib,
  68. 'library': self.func_library,
  69. 'build_target': self.func_build_target,
  70. 'custom_target': self.func_custom_target,
  71. 'run_target': self.func_run_target,
  72. 'subdir': self.func_subdir,
  73. 'set_variable': self.func_set_variable,
  74. 'get_variable': self.func_get_variable,
  75. 'is_variable': self.func_is_variable,
  76. })
  77. def func_do_nothing(self, node, args, kwargs):
  78. return True
  79. def method_call(self, node):
  80. return True
  81. def func_executable(self, node, args, kwargs):
  82. if args[0] == self.targetname:
  83. if self.operation == ADD_SOURCE:
  84. self.add_source_to_target(node, args, kwargs)
  85. elif self.operation == REMOVE_SOURCE:
  86. self.remove_source_from_target(node, args, kwargs)
  87. else:
  88. raise NotImplementedError('Bleep bloop')
  89. return MockExecutable()
  90. def func_static_lib(self, node, args, kwargs):
  91. return MockStaticLibrary()
  92. def func_shared_lib(self, node, args, kwargs):
  93. return MockSharedLibrary()
  94. def func_library(self, node, args, kwargs):
  95. return self.func_shared_lib(node, args, kwargs)
  96. def func_custom_target(self, node, args, kwargs):
  97. return MockCustomTarget()
  98. def func_run_target(self, node, args, kwargs):
  99. return MockRunTarget()
  100. def func_subdir(self, node, args, kwargs):
  101. prev_subdir = self.subdir
  102. subdir = os.path.join(prev_subdir, args[0])
  103. self.subdir = subdir
  104. buildfilename = os.path.join(self.subdir, environment.build_filename)
  105. absname = os.path.join(self.source_root, buildfilename)
  106. if not os.path.isfile(absname):
  107. self.subdir = prev_subdir
  108. raise InterpreterException('Nonexistent build def file %s.' % buildfilename)
  109. with open(absname, encoding='utf8') as f:
  110. code = f.read()
  111. assert(isinstance(code, str))
  112. try:
  113. codeblock = mparser.Parser(code, self.subdir).parse()
  114. self.asts[subdir] = codeblock
  115. except mesonlib.MesonException as me:
  116. me.file = buildfilename
  117. raise me
  118. self.evaluate_codeblock(codeblock)
  119. self.subdir = prev_subdir
  120. def func_files(self, node, args, kwargs):
  121. if not isinstance(args, list):
  122. return [args]
  123. return args
  124. def evaluate_arithmeticstatement(self, cur):
  125. return 0
  126. def evaluate_plusassign(self, node):
  127. return 0
  128. def evaluate_indexing(self, node):
  129. return 0
  130. def reduce_arguments(self, args):
  131. assert(isinstance(args, mparser.ArgumentNode))
  132. if args.incorrect_order():
  133. raise InvalidArguments('All keyword arguments must be after positional arguments.')
  134. return args.arguments, args.kwargs
  135. def transform(self):
  136. self.load_root_meson_file()
  137. self.asts[''] = self.ast
  138. self.sanity_check_ast()
  139. self.parse_project()
  140. self.run()
  141. def add_source(self, targetname, filename):
  142. self.operation = ADD_SOURCE
  143. self.targetname = targetname
  144. self.filename = filename
  145. self.transform()
  146. def remove_source(self, targetname, filename):
  147. self.operation = REMOVE_SOURCE
  148. self.targetname = targetname
  149. self.filename = filename
  150. self.transform()
  151. def unknown_function_called(self, func_name):
  152. mlog.warning('Unknown function called: ' + func_name)
  153. def add_source_to_target(self, node, args, kwargs):
  154. namespan = node.args.arguments[0].bytespan
  155. buildfilename = os.path.join(self.source_root, self.subdir, environment.build_filename)
  156. raw_data = open(buildfilename, 'r').read()
  157. updated = raw_data[0:namespan[1]] + (", '%s'" % self.filename) + raw_data[namespan[1]:]
  158. open(buildfilename, 'w').write(updated)
  159. sys.exit(0)
  160. def remove_argument_item(self, args, i):
  161. assert(isinstance(args, mparser.ArgumentNode))
  162. namespan = args.arguments[i].bytespan
  163. # Usually remove the comma after this item but if it is
  164. # the last argument, we need to remove the one before.
  165. if i >= len(args.commas):
  166. i -= 1
  167. if i < 0:
  168. commaspan = (0, 0) # Removed every entry in the list.
  169. else:
  170. commaspan = args.commas[i].bytespan
  171. if commaspan[0] < namespan[0]:
  172. commaspan, namespan = namespan, commaspan
  173. buildfilename = os.path.join(self.source_root, args.subdir, environment.build_filename)
  174. raw_data = open(buildfilename, 'r').read()
  175. intermediary = raw_data[0:commaspan[0]] + raw_data[commaspan[1]:]
  176. updated = intermediary[0:namespan[0]] + intermediary[namespan[1]:]
  177. open(buildfilename, 'w').write(updated)
  178. sys.exit(0)
  179. def hacky_find_and_remove(self, node_to_remove):
  180. for a in self.asts[node_to_remove.subdir].lines:
  181. if a.lineno == node_to_remove.lineno:
  182. if isinstance(a, mparser.AssignmentNode):
  183. v = a.value
  184. if not isinstance(v, mparser.ArrayNode):
  185. raise NotImplementedError('Not supported yet, bro.')
  186. args = v.args
  187. for i in range(len(args.arguments)):
  188. if isinstance(args.arguments[i], mparser.StringNode) and self.filename == args.arguments[i].value:
  189. self.remove_argument_item(args, i)
  190. raise NotImplementedError('Sukkess')
  191. def remove_source_from_target(self, node, args, kwargs):
  192. for i in range(1, len(node.args)):
  193. # Is file name directly in function call as a string.
  194. if isinstance(node.args.arguments[i], mparser.StringNode) and self.filename == node.args.arguments[i].value:
  195. self.remove_argument_item(node.args, i)
  196. # Is file name in a variable that gets expanded here.
  197. if isinstance(node.args.arguments[i], mparser.IdNode):
  198. avar = self.get_variable(node.args.arguments[i].value)
  199. if not isinstance(avar, list):
  200. raise NotImplementedError('Non-arrays not supported yet, sorry.')
  201. for entry in avar:
  202. if isinstance(entry, mparser.StringNode) and entry.value == self.filename:
  203. self.hacky_find_and_remove(entry)
  204. sys.exit('Could not find source %s in target %s.' % (self.filename, args[0]))