Merge pull request #2731 from mesonbuild/disabler

Created disabler object type
pull/2735/head
Jussi Pakkanen 8 years ago committed by GitHub
commit bc83c58d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 65
      docs/markdown/Disabler.md
  2. 13
      docs/markdown/Reference-manual.md
  3. 33
      docs/markdown/snippets/disabler.md
  4. 1
      docs/sitemap.txt
  5. 1
      mesonbuild/coredata.py
  6. 8
      mesonbuild/interpreter.py
  7. 87
      mesonbuild/interpreterbase.py
  8. 34
      test cases/common/168 disabler/meson.build

@ -0,0 +1,65 @@
---
short-description: Disabling options
...
# Disabling parts of the build (available since 0.44.0)
The following is a common fragment found in many projects:
```meson
dep = dependency('foo')
# In some different directory
lib = shared_library('mylib', 'mylib.c',
dependencies : dep)
# And ín a third directory
exe = executable('mytest', 'mytest.c',
link_with : lib)
test('mytest', exe)
```
This works fine but gets a bit inflexible when you want to make this
part of the build optional. Basically it reduces to adding `if/else`
statements around all target invocations. Meson provides a simpler way
of achieving the same with a disabler object.
A disabler object is created with the `disabler` function:
```meson
d = disabler()
```
The only thing you can do to a disabler object is to ask if it has
been found:
```meson
f = d.found() # returns false
```
Any other statement that uses a disabler object will immediately
return a disabler. For example assuming that `d` contains a disabler
object then
```meson
d2 = some_func(d) # value of d2 will be disabler
d3 = true or d2 # value of d3 will be disabler
if d # neither branch is evaluated
```
Thus to disable every target that depends on the dependency given
above, you can do something like this:
```meson
if use_foo_feature
d = dependency('foo')
else
d = disabler()
endif
```
This concentrates the handling of this option in one place and other
build definition files do not need to be sprinkled with `if`
statements.

@ -326,6 +326,10 @@ some branches of a conditional.
The returned object also has methods that are documented in the
[object methods section](#dependency-object) below.
### disabler()
Returns a [disabler object]((#disabler-object)). Added in 0.44.0.
### error()
``` meson
@ -1631,6 +1635,15 @@ an external dependency with the following methods:
- `version()` is the version number as a string, for example `1.2.8`
### `disabler` object
A disabler object is an object that behaves in much the same way as
NaN numbers do in floating point math. That is when used in any
statement (function call, logical op, etc) they will cause the
statement evaluation to immediately short circuit to return a disabler
object. A disabler object has one method:
- `found()`, always returns `false`
### `external program` object

@ -0,0 +1,33 @@
# Added disabler object
A disabler object is a new kind of object that has very specific
semantics. If it is used as part of any other operation such as an
argument to a function call, logical operations etc, it will cause the
operation to not be evaluated. Instead the return value of said
operation will also be the disabler object.
For example if you have an setup like this:
```meson
dep = dependency('foo')
lib = shared_library('mylib', 'mylib.c',
dependencies : dep)
exe = executable('mytest', 'mytest.c',
link_with : lib)
test('mytest', exe)
```
If you replace the dependency with a disabler object like this:
```meson
dep = disabler()
lib = shared_library('mylib', 'mylib.c',
dependencies : dep)
exe = executable('mytest', 'mytest.c',
link_with : lib)
test('mytest', exe)
```
Then the shared library, executable and unit test are not
created. This is a handy mechanism to cut down on the number of `if`
statements.

@ -26,6 +26,7 @@ index.md
Localisation.md
Build-options.md
Subprojects.md
Disabler.md
Modules.md
Gnome-module.md
i18n-module.md

@ -125,6 +125,7 @@ class UserComboOption(UserOption):
raise MesonException('Value %s not one of accepted values.' % value)
return value
class UserStringArrayOption(UserOption):
def __init__(self, name, description, value, **kwargs):
super().__init__(name, description, kwargs.get('choices', []))

@ -27,7 +27,7 @@ from .dependencies import InternalDependency, Dependency, DependencyException
from .interpreterbase import InterpreterBase
from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs, permittedKwargs
from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode
from .interpreterbase import InterpreterObject, MutableInterpreterObject
from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler
from .modules import ModuleReturnValue
import os, sys, shutil, uuid
@ -1451,6 +1451,7 @@ class Interpreter(InterpreterBase):
'custom_target': self.func_custom_target,
'declare_dependency': self.func_declare_dependency,
'dependency': self.func_dependency,
'disabler': self.func_disabler,
'environment': self.func_environment,
'error': self.func_error,
'executable': self.func_executable,
@ -2203,6 +2204,11 @@ to directly access options of other subprojects.''')
self.coredata.deps[identifier] = dep
return DependencyHolder(dep)
@noKwargs
@noPosargs
def func_disabler(self, node, args, kwargs):
return Disabler()
def get_subproject_infos(self, kwargs):
fbinfo = kwargs['fallback']
check_stringlist(fbinfo)

@ -100,6 +100,29 @@ class MutableInterpreterObject(InterpreterObject):
def __init__(self):
super().__init__()
class Disabler(InterpreterObject):
def __init__(self):
super().__init__()
self.methods.update({'found': self.found_method})
def found_method(self, args, kwargs):
return False
def is_disabler(i):
return isinstance(i, Disabler)
def is_disabled(args, kwargs):
for i in args:
if isinstance(i, Disabler):
return True
for i in kwargs.values():
if isinstance(i, Disabler):
return True
if isinstance(i, list):
for j in i:
if isinstance(j, Disabler):
return True
return False
class InterpreterBase:
def __init__(self, source_root, subdir):
@ -230,6 +253,8 @@ class InterpreterBase:
assert(isinstance(node, mparser.IfClauseNode))
for i in node.ifs:
result = self.evaluate_statement(i.condition)
if is_disabler(result):
return result
if not(isinstance(result, bool)):
raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result))
if result:
@ -240,7 +265,11 @@ class InterpreterBase:
def evaluate_comparison(self, node):
val1 = self.evaluate_statement(node.left)
if is_disabler(val1):
return val1
val2 = self.evaluate_statement(node.right)
if is_disabler(val2):
return val2
if node.ctype == '==':
return val1 == val2
elif node.ctype == '!=':
@ -267,35 +296,49 @@ class InterpreterBase:
def evaluate_andstatement(self, cur):
l = self.evaluate_statement(cur.left)
if is_disabler(l):
return l
if not isinstance(l, bool):
raise InterpreterException('First argument to "and" is not a boolean.')
if not l:
return False
r = self.evaluate_statement(cur.right)
if is_disabler(r):
return r
if not isinstance(r, bool):
raise InterpreterException('Second argument to "and" is not a boolean.')
return r
def evaluate_orstatement(self, cur):
l = self.evaluate_statement(cur.left)
if is_disabler(l):
return l
if not isinstance(l, bool):
raise InterpreterException('First argument to "or" is not a boolean.')
if l:
return True
r = self.evaluate_statement(cur.right)
if is_disabler(r):
return r
if not isinstance(r, bool):
raise InterpreterException('Second argument to "or" is not a boolean.')
return r
def evaluate_uminusstatement(self, cur):
v = self.evaluate_statement(cur.value)
if is_disabler(v):
return v
if not isinstance(v, int):
raise InterpreterException('Argument to negation is not an integer.')
return -v
def evaluate_arithmeticstatement(self, cur):
l = self.evaluate_statement(cur.left)
if is_disabler(l):
return l
r = self.evaluate_statement(cur.right)
if is_disabler(r):
return r
if cur.operation == 'add':
try:
@ -324,6 +367,8 @@ class InterpreterBase:
def evaluate_ternary(self, node):
assert(isinstance(node, mparser.TernaryNode))
result = self.evaluate_statement(node.condition)
if is_disabler(result):
return result
if not isinstance(result, bool):
raise InterpreterException('Ternary condition is not boolean.')
if result:
@ -335,6 +380,8 @@ class InterpreterBase:
assert(isinstance(node, mparser.ForeachClauseNode))
varname = node.varname.value
items = self.evaluate_statement(node.items)
if is_disabler(items):
return items
if not isinstance(items, list):
raise InvalidArguments('Items of foreach loop is not an array')
for item in items:
@ -345,6 +392,9 @@ class InterpreterBase:
assert(isinstance(node, mparser.PlusAssignmentNode))
varname = node.var_name
addition = self.evaluate_statement(node.value)
if is_disabler(addition):
set_variable(varname, addition)
return
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
old_variable = self.get_variable(varname)
@ -369,6 +419,8 @@ class InterpreterBase:
def evaluate_indexing(self, node):
assert(isinstance(node, mparser.IndexNode))
iobject = self.evaluate_statement(node.iobject)
if is_disabler(iobject):
return iobject
if not hasattr(iobject, '__getitem__'):
raise InterpreterException(
'Tried to index an object that doesn\'t support indexing.')
@ -383,6 +435,8 @@ class InterpreterBase:
def function_call(self, node):
func_name = node.func_name
(posargs, kwargs) = self.reduce_arguments(node.args)
if is_disabled(posargs, kwargs):
return Disabler()
if func_name in self.funcs:
return self.funcs[func_name](node, self.flatten(posargs), kwargs)
else:
@ -404,18 +458,26 @@ class InterpreterBase:
if isinstance(obj, int):
return self.int_method_call(obj, method_name, args)
if isinstance(obj, list):
return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0])
return self.array_method_call(obj, method_name, args)
if isinstance(obj, mesonlib.File):
raise InvalidArguments('File object "%s" is not callable.' % obj)
if not isinstance(obj, InterpreterObject):
raise InvalidArguments('Variable "%s" is not callable.' % object_name)
(args, kwargs) = self.reduce_arguments(args)
# Special case. This is the only thing you can do with a disabler
# object. Every other use immediately returns the disabler object.
if isinstance(obj, Disabler) and method_name == 'found':
return False
if is_disabled(args, kwargs):
return Disabler()
if method_name == 'extract_objects':
self.validate_extraction(obj.held_object)
return obj.method_call(method_name, self.flatten(args), kwargs)
def bool_method_call(self, obj, method_name, args):
(posargs, _) = self.reduce_arguments(args)
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
if method_name == 'to_string':
if not posargs:
if obj:
@ -438,7 +500,9 @@ class InterpreterBase:
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name)
def int_method_call(self, obj, method_name, args):
(posargs, _) = self.reduce_arguments(args)
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
if method_name == 'is_even':
if not posargs:
return obj % 2 == 0
@ -471,7 +535,9 @@ class InterpreterBase:
return None
def string_method_call(self, obj, method_name, args):
(posargs, _) = self.reduce_arguments(args)
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
if method_name == 'strip':
s = self._get_one_string_posarg(posargs, 'strip')
if s is not None:
@ -523,16 +589,19 @@ class InterpreterBase:
raise InvalidCode('Unknown function "%s".' % func_name)
def array_method_call(self, obj, method_name, args):
(posargs, kwargs) = self.reduce_arguments(args)
if is_disabled(posargs, kwargs):
return Disabler()
if method_name == 'contains':
return self.check_contains(obj, args)
return self.check_contains(obj, posargs)
elif method_name == 'length':
return len(obj)
elif method_name == 'get':
index = args[0]
index = posargs[0]
fallback = None
if len(args) == 2:
fallback = args[1]
elif len(args) > 2:
if len(posargs) == 2:
fallback = posargs[1]
elif len(posargs) > 2:
m = 'Array method \'get()\' only takes two arguments: the ' \
'index and an optional fallback value if the index is ' \
'out of range.'

@ -0,0 +1,34 @@
project('dolphin option', 'c')
d = disabler()
d2 = dependency(d)
d3 = (d == d2)
d4 = d + 0
d5 = d2 or true
assert(d, 'Disabler did not cause this to be skipped.')
assert(d2, 'Function laundered disabler did not cause this to be skipped.')
assert(d3, 'Disabler comparison should yield disabler and thus this would not be called.')
assert(d4, 'Disabler addition should yield disabler and thus this would not be called.')
assert(d5, 'Disabler logic op should yield disabler and thus this would not be called.')
number = 0
if d
number = 1
else
number = 2
endif
assert(d == 0, 'Plain if handled incorrectly, value should be 0 but is @0@'.format(number))
if d.found()
number = 1
else
number = 2
endif
assert(d == 1, 'If found handled incorrectly, value should be 1 but is @0@'.format(number))
Loading…
Cancel
Save