Allow creation of tree structured menus.

optiontree
Jussi Pakkanen 9 years ago
parent 7830cb61c3
commit a29219d1c3
  1. 26
      mesonbuild/coredata.py
  2. 4
      mesonbuild/environment.py
  3. 2
      mesonbuild/interpreter.py
  4. 83
      mesonbuild/mintro.py
  5. 52
      mesonbuild/optinterpreter.py
  6. 4
      test cases/common/47 options/meson_options.txt

@ -18,19 +18,26 @@ from .mesonlib import MesonException, default_libdir, default_libexecdir, defaul
version = '0.34.0.dev1'
backendlist = ['ninja', 'vs2010', 'vs2015', 'xcode']
class SubOption:
def __init__(self, name, parent, description):
self.name = name
self.parent = parent
self.description = description
class UserOption:
def __init__(self, name, description, choices):
def __init__(self, name, description, choices, parent=None):
super().__init__()
self.name = name
self.choices = choices
self.description = description
self.parent = parent
def parse_string(self, valuestring):
return valuestring
class UserStringOption(UserOption):
def __init__(self, name, description, value, choices=None):
super().__init__(name, description, choices)
def __init__(self, name, description, value, parent=None, choices=None):
super().__init__(name, description, choices, parent)
self.set_value(value)
def validate(self, value):
@ -47,8 +54,8 @@ class UserStringOption(UserOption):
self.value = newvalue
class UserBooleanOption(UserOption):
def __init__(self, name, description, value):
super().__init__(name, description, [ True, False ])
def __init__(self, name, description, value, parent=None):
super().__init__(name, description, [ True, False ], parent)
self.set_value(value)
def tobool(self, thing):
@ -74,8 +81,8 @@ class UserBooleanOption(UserOption):
return self.value
class UserComboOption(UserOption):
def __init__(self, name, description, choices, value):
super().__init__(name, description, choices)
def __init__(self, name, description, choices, value, parent=None):
super().__init__(name, description, choices, parent)
if not isinstance(self.choices, list):
raise MesonException('Combo choices must be an array.')
for i in self.choices:
@ -90,8 +97,8 @@ class UserComboOption(UserOption):
self.value = newvalue
class UserStringArrayOption(UserOption):
def __init__(self, name, description, value, **kwargs):
super().__init__(name, description, kwargs.get('choices', []))
def __init__(self, name, description, value, parent=None, **kwargs):
super().__init__(name, description, kwargs.get('choices', []), parent)
self.set_value(value)
def set_value(self, newvalue):
@ -120,6 +127,7 @@ class CoreData():
self.version = version
self.init_builtins(options)
self.user_options = {}
self.suboptions = {} # In GUIs these would be called "submenus".
self.compiler_options = {}
self.base_options = {}
self.external_args = {} # These are set from "the outside" with e.g. mesonconf

@ -216,7 +216,9 @@ class Environment():
previous_is_plaind = i == '-D'
return False
def merge_options(self, options):
def merge_options(self, options, suboptions):
for (name, value) in suboptions.items():
self.coredata.suboptions[name] = value
for (name, value) in options.items():
if name not in self.coredata.user_options:
self.coredata.user_options[name] = value

@ -1028,7 +1028,7 @@ class Interpreter():
oi = optinterpreter.OptionInterpreter(self.subproject, \
self.build.environment.cmd_line_options.projectoptions)
oi.process(option_file)
self.build.environment.merge_options(oi.options)
self.build.environment.merge_options(oi.options, oi.suboptions)
mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
if not os.path.isfile(mesonfile):
raise InvalidArguments('Missing Meson file in %s' % mesonfile)

@ -99,34 +99,71 @@ def list_buildoptions(coredata, builddata):
'type' : 'boolean',
'description' : 'Unity build',
'name' : 'unity'}
optlist = [buildtype, strip, unity]
add_keys(optlist, coredata.user_options)
add_keys(optlist, coredata.compiler_options)
add_keys(optlist, coredata.base_options)
print(json.dumps(optlist))
all_opt = []
all_opt.append({'name' : 'core',
'description' : 'Core options',
'type' : 'suboption',
'value' : [buildtype, strip, unity],
})
all_opt.append({'name': 'user',
'description' : 'User defined options',
'type' : 'suboption',
'value' : build_usertree(coredata.suboptions, coredata.user_options),
})
all_opt.append({'name' : 'compilers',
'description' : 'Options for compilers',
'type' : 'suboption',
'value' : get_keys(coredata.compiler_options),
})
all_opt.append({'name': 'base',
'description' : 'Base options',
'type' : 'suboption',
'value' : get_keys(coredata.base_options),
})
print(json.dumps(all_opt, indent=2))
def build_usertree(suboptions, user_options, subbranch=None):
current = []
current_suboptions = [x for x in suboptions.values() if x.parent == subbranch]
current_options = [x for x in user_options.values() if x.parent == subbranch]
for so in current_suboptions:
subentry = {'type' : 'subobject',
'value' : build_usertree(suboptions, user_options, so.name),
'description' : so.description,
'name' : so.name
}
current.append(subentry)
for opt in current_options:
current.append(opt2dict(opt.name, opt))
return current
def opt2dict(key, opt):
optdict = {}
optdict['name'] = key
optdict['value'] = opt.value
if isinstance(opt, coredata.UserStringOption):
typestr = 'string'
elif isinstance(opt, coredata.UserBooleanOption):
typestr = 'boolean'
elif isinstance(opt, coredata.UserComboOption):
optdict['choices'] = opt.choices
typestr = 'combo'
elif isinstance(opt, coredata.UserStringArrayOption):
typestr = 'stringarray'
else:
raise RuntimeError("Unknown option type")
optdict['type'] = typestr
optdict['description'] = opt.description
return optdict
def add_keys(optlist, options):
def get_keys(options):
optlist = []
keys = list(options.keys())
keys.sort()
for key in keys:
opt = options[key]
optdict = {}
optdict['name'] = key
optdict['value'] = opt.value
if isinstance(opt, coredata.UserStringOption):
typestr = 'string'
elif isinstance(opt, coredata.UserBooleanOption):
typestr = 'boolean'
elif isinstance(opt, coredata.UserComboOption):
optdict['choices'] = opt.choices
typestr = 'combo'
elif isinstance(opt, coredata.UserStringArrayOption):
typestr = 'stringarray'
else:
raise RuntimeError("Unknown option type")
optdict['type'] = typestr
optdict['description'] = opt.description
optlist.append(optdict)
optlist.append(opt2dict(key, opt))
return optlist
def list_buildsystem_files(coredata, builddata):
src_dir = builddata.environment.get_source_dir()

@ -44,14 +44,14 @@ class OptionException(mesonlib.MesonException):
optname_regex = re.compile('[^a-zA-Z0-9_-]')
def StringParser(name, description, kwargs):
def StringParser(name, description, parent, kwargs):
return coredata.UserStringOption(name, description,
kwargs.get('value', ''), kwargs.get('choices', []))
kwargs.get('value', ''), kwargs.get('choices', []), parent)
def BooleanParser(name, description, kwargs):
return coredata.UserBooleanOption(name, description, kwargs.get('value', True))
def BooleanParser(name, description, parent, kwargs):
return coredata.UserBooleanOption(name, description, kwargs.get('value', True), parent)
def ComboParser(name, description, kwargs):
def ComboParser(name, description, parent, kwargs):
if 'choices' not in kwargs:
raise OptionException('Combo option missing "choices" keyword.')
choices = kwargs['choices']
@ -60,7 +60,7 @@ def ComboParser(name, description, kwargs):
for i in choices:
if not isinstance(i, str):
raise OptionException('Combo choice elements must be strings.')
return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0]))
return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0]), parent)
option_types = {'string' : StringParser,
'boolean' : BooleanParser,
@ -70,6 +70,7 @@ option_types = {'string' : StringParser,
class OptionInterpreter:
def __init__(self, subproject, command_line_options):
self.options = {}
self.suboptions = {}
self.subproject = subproject
self.cmd_line_options = {}
for o in command_line_options:
@ -123,16 +124,35 @@ class OptionInterpreter:
if not isinstance(node, mparser.FunctionNode):
raise OptionException('Option file may only contain option definitions')
func_name = node.func_name
if func_name != 'option':
raise OptionException('Only calls to option() are allowed in option files.')
(posargs, kwargs) = self.reduce_arguments(node.args)
if len(posargs) != 1:
raise OptionException('Function call must have one (and only one) positional argument')
if func_name == 'option':
return self.evaluate_option(posargs, kwargs)
elif func_name == 'suboption':
return self.evaluate_suboption(posargs, kwargs)
else:
raise OptionException('Only calls to option() or suboption() are allowed in option files.')
def evaluate_suboption(self, posargs, kwargs):
subopt_name = posargs[0]
parent_name = kwargs.get('parent', None)
if self.subproject != '':
subopt_name = self.subproject + ':' + subopt_name
if parent_name is not None:
parent_name = self.subproject + ':' + parent_name
if subopt_name in self.suboptions:
raise OptionException('Tried to redefine suboption %s.' % subopt_name)
description = kwargs.get('description', subopt_name)
so = coredata.SubOption(subopt_name, parent_name, description)
self.suboptions[subopt_name] = so
def evaluate_option(self, posargs, kwargs):
if 'type' not in kwargs:
raise OptionException('Option call missing mandatory "type" keyword argument')
opt_type = kwargs['type']
if not opt_type in option_types:
raise OptionException('Unknown type %s.' % opt_type)
if len(posargs) != 1:
raise OptionException('Option() must have one (and only one) positional argument')
opt_name = posargs[0]
if not isinstance(opt_name, str):
raise OptionException('Positional argument must be a string.')
@ -140,9 +160,19 @@ class OptionInterpreter:
raise OptionException('Option names can only contain letters, numbers or dashes.')
if is_invalid_name(opt_name):
raise OptionException('Option name %s is reserved.' % opt_name)
parent = kwargs.get('parent', None)
if parent is not None:
if not isinstance(parent, str):
raise OptionException('Parent, if set, must be a string.')
if self.subproject != '':
opt_name = self.subproject + ':' + opt_name
opt = option_types[opt_type](opt_name, kwargs.get('description', ''), kwargs)
if parent is not None:
parent = self.subproject + ':' + parent
if opt_name in self.options:
raise OptionException('Tried to redeclare option named %s.' % opt_name)
if parent is not None and parent not in self.suboptions:
raise OptionException('Parent %s of option %s is unknown.' % (parent, opt_name))
opt = option_types[opt_type](opt_name, kwargs.get('description', ''), parent, kwargs)
if opt.description == '':
opt.description = opt_name
if opt_name in self.cmd_line_options:

@ -1,3 +1,7 @@
option('testoption', type : 'string', value : 'optval', description : 'An option to do something')
option('other_one', type : 'boolean', value : false)
option('combo_opt', type : 'combo', choices : ['one', 'two', 'combo'], value : 'combo')
suboption('suboptions')
option('subbool', type : 'boolean', parent : 'suboptions', description : 'An option in a submenu.')
suboption('subsuboptions', parent : 'suboptions')
option('subsubbool', type : 'boolean', parent : 'subsuboptions', description : 'A subsubmenuoption.')

Loading…
Cancel
Save