optinterpreter.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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.cmd_line_options = {}
  62. for o in command_line_options:
  63. (key, value) = o.split('=', 1)
  64. self.cmd_line_options[key] = value
  65. def process(self, option_file):
  66. try:
  67. with open(option_file, 'r', encoding='utf8') as f:
  68. ast = mparser.Parser(f.read()).parse()
  69. except mesonlib.MesonException as me:
  70. me.file = option_file
  71. raise me
  72. if not isinstance(ast, mparser.CodeBlockNode):
  73. e = OptionException('Option file is malformed.')
  74. e.lineno = ast.lineno()
  75. raise e
  76. for cur in ast.lines:
  77. try:
  78. self.evaluate_statement(cur)
  79. except Exception as e:
  80. e.lineno = cur.lineno
  81. e.colno = cur.colno
  82. e.file = os.path.join('meson_options.txt')
  83. raise e
  84. def reduce_single(self, arg):
  85. if isinstance(arg, str):
  86. return arg
  87. elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,
  88. mparser.NumberNode)):
  89. return arg.value
  90. elif isinstance(arg, mparser.ArrayNode):
  91. return [self.reduce_single(curarg) for curarg in arg.args.arguments]
  92. else:
  93. raise OptionException('Arguments may only be string, int, bool, or array of those.')
  94. def reduce_arguments(self, args):
  95. assert(isinstance(args, mparser.ArgumentNode))
  96. if args.incorrect_order():
  97. raise OptionException('All keyword arguments must be after positional arguments.')
  98. reduced_pos = [self.reduce_single(arg) for arg in args.arguments]
  99. reduced_kw = {}
  100. for key in args.kwargs.keys():
  101. if not isinstance(key, str):
  102. raise OptionException('Keyword argument name is not a string.')
  103. a = args.kwargs[key]
  104. reduced_kw[key] = self.reduce_single(a)
  105. return (reduced_pos, reduced_kw)
  106. def evaluate_statement(self, node):
  107. if not isinstance(node, mparser.FunctionNode):
  108. raise OptionException('Option file may only contain option definitions')
  109. func_name = node.func_name
  110. if func_name != 'option':
  111. raise OptionException('Only calls to option() are allowed in option files.')
  112. (posargs, kwargs) = self.reduce_arguments(node.args)
  113. if 'type' not in kwargs:
  114. raise OptionException('Option call missing mandatory "type" keyword argument')
  115. opt_type = kwargs['type']
  116. if not opt_type in option_types:
  117. raise OptionException('Unknown type %s.' % opt_type)
  118. if len(posargs) != 1:
  119. raise OptionException('Option() must have one (and only one) positional argument')
  120. opt_name = posargs[0]
  121. if not isinstance(opt_name, str):
  122. raise OptionException('Positional argument must be a string.')
  123. if optname_regex.search(opt_name) is not None:
  124. raise OptionException('Option names can only contain letters, numbers or dashes.')
  125. if is_invalid_name(opt_name):
  126. raise OptionException('Option name %s is reserved.' % opt_name)
  127. if self.subproject != '':
  128. opt_name = self.subproject + ':' + opt_name
  129. opt = option_types[opt_type](opt_name, kwargs.get('description', ''), kwargs)
  130. if opt.description == '':
  131. opt.description = opt_name
  132. if opt_name in self.cmd_line_options:
  133. opt.set_value(opt.parse_string(self.cmd_line_options[opt_name]))
  134. self.options[opt_name] = opt