diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2017-12-05 01:10:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-05 01:10:50 +0200 |
commit | bc83c58d37421b84c5420356a79e04ade2b851a7 (patch) | |
tree | 1c47671d6d458ac4d7ec367d3cac716abedf8da8 | |
parent | 87e6201214eda0941d2a2279e12a575fc27d21bb (diff) | |
parent | d3dcef7efc1df3b7a645eb6dc75c4a66a9131cb9 (diff) | |
download | meson-bc83c58d37421b84c5420356a79e04ade2b851a7.zip meson-bc83c58d37421b84c5420356a79e04ade2b851a7.tar.gz meson-bc83c58d37421b84c5420356a79e04ade2b851a7.tar.bz2 |
Merge pull request #2731 from mesonbuild/disabler
Created disabler object type
-rw-r--r-- | docs/markdown/Disabler.md | 65 | ||||
-rw-r--r-- | docs/markdown/Reference-manual.md | 13 | ||||
-rw-r--r-- | docs/markdown/snippets/disabler.md | 33 | ||||
-rw-r--r-- | docs/sitemap.txt | 1 | ||||
-rw-r--r-- | mesonbuild/coredata.py | 1 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 8 | ||||
-rw-r--r-- | mesonbuild/interpreterbase.py | 89 | ||||
-rw-r--r-- | test cases/common/168 disabler/meson.build | 34 |
8 files changed, 233 insertions, 11 deletions
diff --git a/docs/markdown/Disabler.md b/docs/markdown/Disabler.md new file mode 100644 index 0000000..2d50c5c --- /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 4be06c4..ac83152 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 0000000..1323048 --- /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 6b155af..9c86d60 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 376570c..302c286 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 86d25ea..f33d437 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 7ccc8b2..91f4bd3 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 0000000..7ca82b7 --- /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)) + + |