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
version of Meson. In that case you can call `mesonconf` instead, but
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-2017 The Meson development team
# Copyright 2012-2018 The Meson development team
# Licensed under the Apache License, Version 2.0 (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'
backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode']
default_yielding = False
class UserOption:
def __init__(self, name, description, choices):
def __init__(self, name, description, choices, yielding):
super().__init__()
self.name = name
self.choices = choices
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
# "cleaned" or "native" version. For example the Boolean
@ -39,8 +45,8 @@ class UserOption:
raise RuntimeError('Derived option class did not override validate_value.')
class UserStringOption(UserOption):
def __init__(self, name, description, value, choices=None):
super().__init__(name, description, choices)
def __init__(self, name, description, value, choices=None, yielding=None):
super().__init__(name, description, choices, yielding)
self.set_value(value)
def validate(self, value):
@ -56,8 +62,8 @@ class UserStringOption(UserOption):
return value
class UserBooleanOption(UserOption):
def __init__(self, name, description, value):
super().__init__(name, description, [True, False])
def __init__(self, name, description, value, yielding=None):
super().__init__(name, description, [True, False], yielding)
self.set_value(value)
def tobool(self, thing):
@ -79,8 +85,8 @@ class UserBooleanOption(UserOption):
return self.tobool(value)
class UserIntegerOption(UserOption):
def __init__(self, name, description, min_value, max_value, value):
super().__init__(name, description, None)
def __init__(self, name, description, min_value, max_value, value, yielding=None):
super().__init__(name, description, [True, False], yielding)
self.min_value = min_value
self.max_value = max_value
self.set_value(value)
@ -112,8 +118,8 @@ class UserIntegerOption(UserOption):
return self.toint(value)
class UserComboOption(UserOption):
def __init__(self, name, description, choices, value):
super().__init__(name, description, choices)
def __init__(self, name, description, choices, value, yielding=None):
super().__init__(name, description, choices, yielding)
if not isinstance(self.choices, list):
raise MesonException('Combo choices must be an array.')
for i in self.choices:
@ -134,7 +140,7 @@ class UserComboOption(UserOption):
class UserArrayOption(UserOption):
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)
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):
if len(args) != 1:
raise InterpreterException('Argument required for get_option.')
optname = args[0]
undecorated_optname = optname = args[0]
if ':' in optname:
raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed
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():
optname = self.subproject + ':' + optname
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:
pass
if optname.endswith('_link_args'):

@ -64,16 +64,21 @@ def permitted_kwargs(permitted):
optname_regex = re.compile('[^a-zA-Z0-9_-]')
@permitted_kwargs({'value'})
@permitted_kwargs({'value', 'yield'})
def StringParser(name, description, kwargs):
return coredata.UserStringOption(name, description,
kwargs.get('value', ''), kwargs.get('choices', []))
return coredata.UserStringOption(name,
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):
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):
if 'choices' not in kwargs:
raise OptionException('Combo option missing "choices" keyword.')
@ -83,9 +88,14 @@ 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]),
kwargs.get('yield', coredata.default_yielding),)
@permitted_kwargs({'value', 'min', 'max'})
@permitted_kwargs({'value', 'min', 'max', 'yield'})
def IntegerParser(name, description, kwargs):
if 'value' not in kwargs:
raise OptionException('Integer option must contain value argument.')
@ -93,9 +103,10 @@ def IntegerParser(name, description, kwargs):
description,
kwargs.get('min', 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):
if 'choices' in kwargs:
choices = kwargs['choices']
@ -110,7 +121,11 @@ def string_array_parser(name, description, kwargs):
value = kwargs.get('value', [])
if not isinstance(value, list):
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,
'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