Bug 1322025 - Allow to combine two DependsFunctions with "|". r=chmanchester
Ideally, it would have been better if it were "or", but it's not
possible to override "or" in python ; __or__ is for "|".
This does feel magic, but it's also shorter than adding something like
@depends_any(), and while we're only adding "|" as of this change, we
can add other operations such as "&" in the future, or __getattr__ for
things like milestone.is_nightly.
An alternative form in moz.configure could require the @depends function
to be called, e.g. "a() | b()" instead of "a | b", but I'm not
particularly convinced that one is less magic than the other.
This feature is hooked up such that b is not resolved if a is true,
although in practice, it will still be resolved in Sandbox.run... but
not when --help is passed. In the long run, the forced resolution of
@depends functions will be removed from Sandbox.run.
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -39,33 +39,42 @@ import mozpack.path as mozpath
class ConfigureError(Exception):
pass
class SandboxDependsFunction(object):
'''Sandbox-visible representation of @depends functions.'''
+ def __init__(self, unsandboxed):
+ self._or = unsandboxed.__or__
+
def __call__(self, *arg, **kwargs):
raise ConfigureError('The `%s` function may not be called'
% self.__name__)
+ def __or__(self, other):
+ if not isinstance(other, SandboxDependsFunction):
+ raise ConfigureError('Can only do binary arithmetic operations '
+ 'with another @depends function.')
+ return self._or(other).sandboxed
+
class DependsFunction(object):
__slots__ = (
'_func', '_name', 'dependencies', 'when', 'sandboxed', 'sandbox',
'_result')
def __init__(self, sandbox, func, dependencies, when=None):
assert isinstance(sandbox, ConfigureSandbox)
assert not inspect.isgeneratorfunction(func)
self._func = func
self._name = func.__name__
self.dependencies = dependencies
- self.sandboxed = wraps(func)(SandboxDependsFunction())
+ self.sandboxed = wraps(func)(SandboxDependsFunction(self))
self.sandbox = sandbox
self.when = when
sandbox._depends[self.sandboxed] = self
# Only @depends functions with a dependency on '--help' are executed
# immediately. Everything else is queued for later execution.
if sandbox._help_option in dependencies:
sandbox._value_for(self)
@@ -100,16 +109,33 @@ class DependsFunction(object):
def __repr__(self):
return '<%s.%s %s(%s)>' % (
self.__class__.__module__,
self.__class__.__name__,
self.name,
', '.join(repr(d) for d in self.dependencies),
)
+ def __or__(self, other):
+ if isinstance(other, SandboxDependsFunction):
+ other = self.sandbox._depends.get(other)
+ assert isinstance(other, DependsFunction)
+ assert self.sandbox is other.sandbox
+ return CombinedDependsFunction(self.sandbox, self.first_true,
+ (self, other))
+
+ @staticmethod
+ def first_true(iterable):
+ # Like the builtin any(), but returns the first element that is true,
+ # instead of True. If none are true, returns the last element.
+ for i in iterable:
+ if i:
+ return i
+ return i
+
class CombinedDependsFunction(DependsFunction):
def __init__(self, sandbox, func, dependencies):
flatten_deps = []
for d in dependencies:
if isinstance(d, CombinedDependsFunction) and d._func is func:
for d2 in d.dependencies:
if d2 not in flatten_deps:
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -1282,11 +1282,45 @@ class TestConfigure(unittest.TestCase):
'--foo is not available in this configuration')
# And similarly doesn't fail when the condition is true.
with self.moz_configure('''
imply_option('--foo', True)
''' + moz_configure):
self.get_config(['--enable-when'])
+ def test_depends_or(self):
+ with self.moz_configure('''
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value or None
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value
+
+ set_config('FOOBAR', foo | bar)
+ '''):
+ config = self.get_config()
+ self.assertEqual(config, {
+ 'FOOBAR': NegativeOptionValue(),
+ })
+
+ config = self.get_config(['--foo=foo'])
+ self.assertEqual(config, {
+ 'FOOBAR': PositiveOptionValue(('foo',)),
+ })
+
+ config = self.get_config(['--bar=bar'])
+ self.assertEqual(config, {
+ 'FOOBAR': PositiveOptionValue(('bar',)),
+ })
+
+ config = self.get_config(['--foo=foo', '--bar=bar'])
+ self.assertEqual(config, {
+ 'FOOBAR': PositiveOptionValue(('foo',)),
+ })
+
if __name__ == '__main__':
main()