optinterpreter.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright 2013-2014 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. from . import mparser
  12. from . import coredata
  13. from . import mesonlib
  14. import os, re
  15. forbidden_option_names = coredata.get_builtin_options()
  16. forbidden_prefixes = {'c_': True,
  17. 'cpp_': True,
  18. 'd_': True,
  19. 'rust_': True,
  20. 'fortran_': True,
  21. 'objc_': True,
  22. 'objcpp_': True,
  23. 'vala_': True,
  24. 'csharp_': True,
  25. 'swift_': True,
  26. 'b_': True,
  27. }
  28. def is_invalid_name(name):
  29. if name in forbidden_option_names:
  30. return True
  31. pref = name.split('_')[0] + '_'
  32. if pref in forbidden_prefixes:
  33. return True
  34. return False
  35. class OptionException(mesonlib.MesonException):
  36. pass
  37. optname_regex = re.compile('[^a-zA-Z0-9_-]')
  38. def StringParser(name, description, kwargs):
  39. return coredata.UserStringOption(name, description,
  40. kwargs.get('value', ''), kwargs.get('choices', []))
  41. def BooleanParser(name, description, kwargs):
  42. return coredata.UserBooleanOption(name, description, kwargs.get('value', True))
  43. def ComboParser(name, description, kwargs):
  44. if 'choices' not in kwargs:
  45. raise OptionException('Combo option missing "choices" keyword.')
  46. choices = kwargs['choices']
  47. if not isinstance(choices, list):
  48. raise OptionException('Combo choices must be an array.')
  49. for i in choices:
  50. if not isinstance(i, str):
  51. raise OptionException('Combo choice elements must be strings.')
  52. return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0]))
  53. option_types = {'string': StringParser,
  54. 'boolean': BooleanParser,
  55. 'combo': ComboParser,
  56. }
  57. class OptionInterpreter:
  58. def __init__(self, subproject, command_line_options):
  59. self.options = {}
  60. self.subproject = subproject
  61. self.sbprefix = subproject + ':'
  62. self.cmd_line_options = {}
  63. for o in command_line_options:
  64. if self.subproject != '': # Strip the beginning.
  65. # Ignore options that aren't for this subproject
  66. if not o.startswith(self.sbprefix):
  67. continue
  68. try:
  69. (key, value) = o.split('=', 1)
  70. except ValueError:
  71. raise OptionException('Option {!r} must have a value separated by equals sign.'.format(o))
  72. # Ignore subproject options if not fetching subproject options
  73. if self.subproject == '' and ':' in key:
  74. continue
  75. self.cmd_line_options[key] = value
  76. def process(self, option_file):
  77. try:
  78. with open(option_file, 'r', encoding='utf8') as f:
  79. ast = mparser.Parser(f.read(), '').parse()
  80. except mesonlib.MesonException as me:
  81. me.file = option_file
  82. raise me
  83. if not isinstance(ast, mparser.CodeBlockNode):
  84. e = OptionException('Option file is malformed.')
  85. e.lineno = ast.lineno()
  86. raise e
  87. for cur in ast.lines:
  88. try:
  89. self.evaluate_statement(cur)
  90. except Exception as e:
  91. e.lineno = cur.lineno
  92. e.colno = cur.colno
  93. e.file = os.path.join('meson_options.txt')
  94. raise e
  95. def reduce_single(self, arg):
  96. if isinstance(arg, str):
  97. return arg
  98. elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,
  99. mparser.NumberNode)):
  100. return arg.value
  101. elif isinstance(arg, mparser.ArrayNode):
  102. return [self.reduce_single(curarg) for curarg in arg.args.arguments]
  103. else:
  104. raise OptionException('Arguments may only be string, int, bool, or array of those.')
  105. def reduce_arguments(self, args):
  106. assert(isinstance(args, mparser.ArgumentNode))
  107. if args.incorrect_order():
  108. raise OptionException('All keyword arguments must be after positional arguments.')
  109. reduced_pos = [self.reduce_single(arg) for arg in args.arguments]
  110. reduced_kw = {}
  111. for key in args.kwargs.keys():
  112. if not isinstance(key, str):
  113. raise OptionException('Keyword argument name is not a string.')
  114. a = args.kwargs[key]
  115. reduced_kw[key] = self.reduce_single(a)
  116. return reduced_pos, reduced_kw
  117. def evaluate_statement(self, node):
  118. if not isinstance(node, mparser.FunctionNode):
  119. raise OptionException('Option file may only contain option definitions')
  120. func_name = node.func_name
  121. if func_name != 'option':
  122. raise OptionException('Only calls to option() are allowed in option files.')
  123. (posargs, kwargs) = self.reduce_arguments(node.args)
  124. if 'type' not in kwargs:
  125. raise OptionException('Option call missing mandatory "type" keyword argument')
  126. opt_type = kwargs['type']
  127. if opt_type not in option_types:
  128. raise OptionException('Unknown type %s.' % opt_type)
  129. if len(posargs) != 1:
  130. raise OptionException('Option() must have one (and only one) positional argument')
  131. opt_name = posargs[0]
  132. if not isinstance(opt_name, str):
  133. raise OptionException('Positional argument must be a string.')
  134. if optname_regex.search(opt_name) is not None:
  135. raise OptionException('Option names can only contain letters, numbers or dashes.')
  136. if is_invalid_name(opt_name):
  137. raise OptionException('Option name %s is reserved.' % opt_name)
  138. if self.subproject != '':
  139. opt_name = self.subproject + ':' + opt_name
  140. opt = option_types[opt_type](opt_name, kwargs.get('description', ''), kwargs)
  141. if opt.description == '':
  142. opt.description = opt_name
  143. if opt_name in self.cmd_line_options:
  144. opt.set_value(opt.parse_string(self.cmd_line_options[opt_name]))
  145. self.options[opt_name] = opt