summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2017-12-05 01:10:50 +0200
committerGitHub <noreply@github.com>2017-12-05 01:10:50 +0200
commitbc83c58d37421b84c5420356a79e04ade2b851a7 (patch)
tree1c47671d6d458ac4d7ec367d3cac716abedf8da8
parent87e6201214eda0941d2a2279e12a575fc27d21bb (diff)
parentd3dcef7efc1df3b7a645eb6dc75c4a66a9131cb9 (diff)
downloadmeson-bc83c58d37421b84c5420356a79e04ade2b851a7.tar.gz
Merge pull request #2731 from mesonbuild/disabler
Created disabler object type
-rw-r--r--docs/markdown/Disabler.md65
-rw-r--r--docs/markdown/Reference-manual.md13
-rw-r--r--docs/markdown/snippets/disabler.md33
-rw-r--r--docs/sitemap.txt1
-rw-r--r--mesonbuild/coredata.py1
-rw-r--r--mesonbuild/interpreter.py8
-rw-r--r--mesonbuild/interpreterbase.py89
-rw-r--r--test cases/common/168 disabler/meson.build34
8 files changed, 233 insertions, 11 deletions
diff --git a/docs/markdown/Disabler.md b/docs/markdown/Disabler.md
new file mode 100644
index 000000000..2d50c5cf8
--- /dev/null
+++ b/docs/markdown/Disabler.md
@@ -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. \ No newline at end of file
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index 4be06c4a2..ac83152cb 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -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
diff --git a/docs/markdown/snippets/disabler.md b/docs/markdown/snippets/disabler.md
new file mode 100644
index 000000000..1323048cc
--- /dev/null
+++ b/docs/markdown/snippets/disabler.md
@@ -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.
diff --git a/docs/sitemap.txt b/docs/sitemap.txt
index 6b155afb7..9c86d60c5 100644
--- a/docs/sitemap.txt
+++ b/docs/sitemap.txt
@@ -26,6 +26,7 @@ index.md
Localisation.md
Build-options.md
Subprojects.md
+ Disabler.md
Modules.md
Gnome-module.md
i18n-module.md
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 376570c08..302c28643 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -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', []))
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 86d25eae0..f33d437bf 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -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)
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
index 7ccc8b2ba..91f4bd3e9 100644
--- a/mesonbuild/interpreterbase.py
+++ b/mesonbuild/interpreterbase.py
@@ -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:
@@ -520,19 +586,22 @@ class InterpreterBase:
raise InterpreterException('Unknown method "%s" for a string.' % method_name)
def unknown_function_called(self, func_name):
- raise InvalidCode('Unknown function "%s".' % func_name)
+ 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.'
diff --git a/test cases/common/168 disabler/meson.build b/test cases/common/168 disabler/meson.build
new file mode 100644
index 000000000..7ca82b786
--- /dev/null
+++ b/test cases/common/168 disabler/meson.build
@@ -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))
+
+