User options can "yield to" a user option of the same name in superproject. Closes ##2853.

pull/3031/head
Jussi Pakkanen 7 years ago
parent 0204895143
commit 54d7817087
  1. 18
      docs/markdown/Build-options.md
  2. 8
      docs/markdown/snippets/yield.md
  3. 30
      mesonbuild/coredata.py
  4. 8
      mesonbuild/interpreter.py
  5. 37
      mesonbuild/optinterpreter.py
  6. 6
      test cases/common/176 yield/meson.build
  7. 2
      test cases/common/176 yield/meson_options.txt
  8. 4
      test cases/common/176 yield/subprojects/sub/meson.build
  9. 2
      test cases/common/176 yield/subprojects/sub/meson_options.txt

@ -108,3 +108,21 @@ double quotes.
**NOTE:** If you cannot call `meson configure` you likely have a old **NOTE:** If you cannot call `meson configure` you likely have a old
version of Meson. In that case you can call `mesonconf` instead, but version of Meson. In that case you can call `mesonconf` instead, but
that is deprecated in newer versions that is deprecated in newer versions
## Yielding to superproject option
Suppose you have a master project and a subproject. In some cases it
might be useful to have an option that has the same value in both of
them. This can be achieved with the `yield` keyword. Suppose you have
an option definition like this:
```meson
option('some_option', type : 'string', value : 'value', yield : true)
```
If you build this project on its own, this option behaves like
usual. However if you build this project as a subproject of another
project which also has an option called `some_option`, then calling
`get_option` returns the value of the superproject. If the value of
`yield` is `false`, `get_option` returns the value of the subproject's
option.

@ -0,0 +1,8 @@
## Yielding subproject option to superproject
Normally project options are specific to the current project. However
sometimes you want to have an option whose value is the same over all
projects. This can be achieved with the new `yield` keyword for
options. When set to `true`, getting the value of this option in
`meson.build` files gets the value from the option with the same name
in the master project (if such an option exists).

@ -1,5 +1,4 @@
# Copyright 2012-2018 The Meson development team
# Copyright 2012-2017 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -25,12 +24,19 @@ import ast
version = '0.45.0.dev1' version = '0.45.0.dev1'
backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode']
default_yielding = False
class UserOption: class UserOption:
def __init__(self, name, description, choices): def __init__(self, name, description, choices, yielding):
super().__init__() super().__init__()
self.name = name self.name = name
self.choices = choices self.choices = choices
self.description = description self.description = description
if yielding is None:
yielding = default_yielding
if not isinstance(yielding, bool):
raise MesonException('Value of "yielding" must be a boolean.')
self.yielding = yielding
# Check that the input is a valid value and return the # Check that the input is a valid value and return the
# "cleaned" or "native" version. For example the Boolean # "cleaned" or "native" version. For example the Boolean
@ -39,8 +45,8 @@ class UserOption:
raise RuntimeError('Derived option class did not override validate_value.') raise RuntimeError('Derived option class did not override validate_value.')
class UserStringOption(UserOption): class UserStringOption(UserOption):
def __init__(self, name, description, value, choices=None): def __init__(self, name, description, value, choices=None, yielding=None):
super().__init__(name, description, choices) super().__init__(name, description, choices, yielding)
self.set_value(value) self.set_value(value)
def validate(self, value): def validate(self, value):
@ -56,8 +62,8 @@ class UserStringOption(UserOption):
return value return value
class UserBooleanOption(UserOption): class UserBooleanOption(UserOption):
def __init__(self, name, description, value): def __init__(self, name, description, value, yielding=None):
super().__init__(name, description, [True, False]) super().__init__(name, description, [True, False], yielding)
self.set_value(value) self.set_value(value)
def tobool(self, thing): def tobool(self, thing):
@ -79,8 +85,8 @@ class UserBooleanOption(UserOption):
return self.tobool(value) return self.tobool(value)
class UserIntegerOption(UserOption): class UserIntegerOption(UserOption):
def __init__(self, name, description, min_value, max_value, value): def __init__(self, name, description, min_value, max_value, value, yielding=None):
super().__init__(name, description, None) super().__init__(name, description, [True, False], yielding)
self.min_value = min_value self.min_value = min_value
self.max_value = max_value self.max_value = max_value
self.set_value(value) self.set_value(value)
@ -112,8 +118,8 @@ class UserIntegerOption(UserOption):
return self.toint(value) return self.toint(value)
class UserComboOption(UserOption): class UserComboOption(UserOption):
def __init__(self, name, description, choices, value): def __init__(self, name, description, choices, value, yielding=None):
super().__init__(name, description, choices) super().__init__(name, description, choices, yielding)
if not isinstance(self.choices, list): if not isinstance(self.choices, list):
raise MesonException('Combo choices must be an array.') raise MesonException('Combo choices must be an array.')
for i in self.choices: for i in self.choices:
@ -134,7 +140,7 @@ class UserComboOption(UserOption):
class UserArrayOption(UserOption): class UserArrayOption(UserOption):
def __init__(self, name, description, value, **kwargs): def __init__(self, name, description, value, **kwargs):
super().__init__(name, description, kwargs.get('choices', [])) super().__init__(name, description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None))
self.set_value(value, user_input=False) self.set_value(value, user_input=False)
def validate(self, value, user_input): def validate(self, value, user_input):

@ -1768,7 +1768,7 @@ external dependencies (including libraries) must go to "dependencies".''')
def func_get_option(self, nodes, args, kwargs): def func_get_option(self, nodes, args, kwargs):
if len(args) != 1: if len(args) != 1:
raise InterpreterException('Argument required for get_option.') raise InterpreterException('Argument required for get_option.')
optname = args[0] undecorated_optname = optname = args[0]
if ':' in optname: if ':' in optname:
raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed
to directly access options of other subprojects.''') to directly access options of other subprojects.''')
@ -1787,7 +1787,11 @@ to directly access options of other subprojects.''')
if not coredata.is_builtin_option(optname) and self.is_subproject(): if not coredata.is_builtin_option(optname) and self.is_subproject():
optname = self.subproject + ':' + optname optname = self.subproject + ':' + optname
try: try:
return self.environment.coredata.user_options[optname].value opt = self.environment.coredata.user_options[optname]
if opt.yielding and ':' in optname:
# If option not present in superproject, keep the original.
opt = self.environment.coredata.user_options.get(undecorated_optname, opt)
return opt.value
except KeyError: except KeyError:
pass pass
if optname.endswith('_link_args'): if optname.endswith('_link_args'):

@ -64,16 +64,21 @@ def permitted_kwargs(permitted):
optname_regex = re.compile('[^a-zA-Z0-9_-]') optname_regex = re.compile('[^a-zA-Z0-9_-]')
@permitted_kwargs({'value'}) @permitted_kwargs({'value', 'yield'})
def StringParser(name, description, kwargs): def StringParser(name, description, kwargs):
return coredata.UserStringOption(name, description, return coredata.UserStringOption(name,
kwargs.get('value', ''), kwargs.get('choices', [])) description,
kwargs.get('value', ''),
kwargs.get('choices', []),
kwargs.get('yield', coredata.default_yielding))
@permitted_kwargs({'value'}) @permitted_kwargs({'value', 'yield'})
def BooleanParser(name, description, kwargs): def BooleanParser(name, description, kwargs):
return coredata.UserBooleanOption(name, description, kwargs.get('value', True)) return coredata.UserBooleanOption(name, description,
kwargs.get('value', True),
kwargs.get('yield', coredata.default_yielding))
@permitted_kwargs({'value', 'choices'}) @permitted_kwargs({'value', 'yiel', 'choices'})
def ComboParser(name, description, kwargs): def ComboParser(name, description, kwargs):
if 'choices' not in kwargs: if 'choices' not in kwargs:
raise OptionException('Combo option missing "choices" keyword.') raise OptionException('Combo option missing "choices" keyword.')
@ -83,9 +88,14 @@ def ComboParser(name, description, kwargs):
for i in choices: for i in choices:
if not isinstance(i, str): if not isinstance(i, str):
raise OptionException('Combo choice elements must be strings.') 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]),
kwargs.get('yield', coredata.default_yielding),)
@permitted_kwargs({'value', 'min', 'max'})
@permitted_kwargs({'value', 'min', 'max', 'yield'})
def IntegerParser(name, description, kwargs): def IntegerParser(name, description, kwargs):
if 'value' not in kwargs: if 'value' not in kwargs:
raise OptionException('Integer option must contain value argument.') raise OptionException('Integer option must contain value argument.')
@ -93,9 +103,10 @@ def IntegerParser(name, description, kwargs):
description, description,
kwargs.get('min', None), kwargs.get('min', None),
kwargs.get('max', None), kwargs.get('max', None),
kwargs['value']) kwargs['value'],
kwargs.get('yield', coredata.default_yielding))
@permitted_kwargs({'value', 'choices'}) @permitted_kwargs({'value', 'yield', 'choices'})
def string_array_parser(name, description, kwargs): def string_array_parser(name, description, kwargs):
if 'choices' in kwargs: if 'choices' in kwargs:
choices = kwargs['choices'] choices = kwargs['choices']
@ -110,7 +121,11 @@ def string_array_parser(name, description, kwargs):
value = kwargs.get('value', []) value = kwargs.get('value', [])
if not isinstance(value, list): if not isinstance(value, list):
raise OptionException('Array choices must be passed as an array.') raise OptionException('Array choices must be passed as an array.')
return coredata.UserArrayOption(name, description, value, choices=choices) return coredata.UserArrayOption(name,
description,
value,
choices=choices,
yielding=kwargs.get('yield', coredata.default_yielding))
option_types = {'string': StringParser, option_types = {'string': StringParser,
'boolean': BooleanParser, 'boolean': BooleanParser,

@ -0,0 +1,6 @@
project('yield_options', 'c')
subproject('sub')
assert(get_option('unshared_option') == 'one', 'Unshared option has wrong value in superproject.')
assert(get_option('shared_option') == 'two', 'Unshared option has wrong value in superproject..')

@ -0,0 +1,2 @@
option('unshared_option', type : 'string', value : 'one')
option('shared_option', type : 'string', value : 'two')

@ -0,0 +1,4 @@
project('subbie', 'c')
assert(get_option('unshared_option') == 'three', 'Unshared option has wrong value in subproject.')
assert(get_option('shared_option') == 'two', 'Shared option has wrong value in subproject.')

@ -0,0 +1,2 @@
option('unshared_option', type : 'string', value : 'three', yield : false)
option('shared_option', type : 'string', value : 'four', yield : true)
Loading…
Cancel
Save