aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2016-08-27 20:25:01 +0300
committerJussi Pakkanen <jpakkane@gmail.com>2016-08-27 20:25:01 +0300
commita29219d1c37fbe52e34b375cc964b7e7019b16aa (patch)
treecc43434603787fd771516d9724d5e83be38b6352
parent7830cb61c39fbaf57933ac403dcdf5007667d87d (diff)
downloadmeson-a29219d1c37fbe52e34b375cc964b7e7019b16aa.zip
meson-a29219d1c37fbe52e34b375cc964b7e7019b16aa.tar.gz
meson-a29219d1c37fbe52e34b375cc964b7e7019b16aa.tar.bz2
Allow creation of tree structured menus.
-rw-r--r--mesonbuild/coredata.py26
-rw-r--r--mesonbuild/environment.py4
-rw-r--r--mesonbuild/interpreter.py2
-rw-r--r--mesonbuild/mintro.py83
-rw-r--r--mesonbuild/optinterpreter.py52
-rw-r--r--test cases/common/47 options/meson_options.txt4
6 files changed, 126 insertions, 45 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 2f1875f..f60cc7b 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -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
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index 341e5e8..9848e69 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -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
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index c9a81fb..f48cc58 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -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)
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index 629b0fc..14f82a6 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -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()
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index b355047..2cd2df2 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -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:
diff --git a/test cases/common/47 options/meson_options.txt b/test cases/common/47 options/meson_options.txt
index 653dd75..f65da86 100644
--- a/test cases/common/47 options/meson_options.txt
+++ b/test cases/common/47 options/meson_options.txt
@@ -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.')