Bug 1363811 - Allow "direct" access to namespace attributes from DependsFunctions. r?chmanchester
To make things simpler in configure code, as well as to allow the linter
to skip bugging about some --help dependencies, we make the following
work:
something.some_attr
where the result is equivalent to, currently:
delayed_getattr(something, 'some_attr')
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -42,16 +42,17 @@ class ConfigureError(Exception):
pass
class SandboxDependsFunction(object):
'''Sandbox-visible representation of @depends functions.'''
def __init__(self, unsandboxed):
self._or = unsandboxed.__or__
self._and = unsandboxed.__and__
+ self._getattr = unsandboxed.__getattr__
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 '
@@ -59,16 +60,19 @@ class SandboxDependsFunction(object):
return self._or(other).sandboxed
def __and__(self, other):
if not isinstance(other, SandboxDependsFunction):
raise ConfigureError('Can only do binary arithmetic operations '
'with another @depends function.')
return self._and(other).sandboxed
+ def __getattr__(self, key):
+ return self._getattr(key).sandboxed
+
def __nonzero__(self):
raise ConfigureError(
'Cannot do boolean operations on @depends functions.')
class DependsFunction(object):
__slots__ = (
'_func', '_name', 'dependencies', 'when', 'sandboxed', 'sandbox',
@@ -154,16 +158,29 @@ class DependsFunction(object):
def and_impl(iterable):
# Applies "and" to all the items of iterable.
# e.g. if iterable contains a, b and c, returns `a and b and c`.
for i in iterable:
if not i:
return i
return i
+ def __getattr__(self, key):
+ if key.startswith('_'):
+ return super(DependsFunction, self).__getattr__(key)
+ # Our function may return None or an object that simply doesn't have
+ # the wanted key. In that case, just return None.
+ return TrivialDependsFunction(
+ self.sandbox, lambda x: getattr(x, key, None), [self], self.when)
+
+
+class TrivialDependsFunction(DependsFunction):
+ '''Like a DependsFunction, but the linter won't expect it to have a
+ dependency on --help ever.'''
+
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/configure/lint.py
+++ b/python/mozbuild/mozbuild/configure/lint.py
@@ -8,16 +8,17 @@ import inspect
from functools import wraps
from StringIO import StringIO
from . import (
CombinedDependsFunction,
ConfigureError,
ConfigureSandbox,
DependsFunction,
SandboxedGlobal,
+ TrivialDependsFunction,
)
from .lint_util import disassemble_as_iter
from mozbuild.util import memoize
class LintSandbox(ConfigureSandbox):
def __init__(self, environ=None, argv=None, stdout=None, stderr=None):
out = StringIO()
@@ -69,17 +70,17 @@ class LintSandbox(ConfigureSandbox):
else:
dep = dep.option
raise ConfigureError(
'%s: The dependency on `%s` is unused.'
% (loc, dep)
)
def _missing_help_dependency(self, obj):
- if isinstance(obj, CombinedDependsFunction):
+ if isinstance(obj, (CombinedDependsFunction, TrivialDependsFunction)):
return False
if isinstance(obj, DependsFunction):
if (self._help_option in obj.dependencies or
obj in (self._always, self._never)):
return False
func, glob = self.unwrap(obj._func)
# We allow missing --help dependencies for functions that:
# - don't use @imports
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -1325,11 +1325,57 @@ class TestConfigure(unittest.TestCase):
[x for x in (foo_opt, bar_opt, baz_opt) if x])
self.assertEqual(config, {
'FOOorBAR': foo_value or bar_value,
'FOOorBARorBAZ': foo_value or bar_value or baz_value,
'FOOandBAR': foo_value and bar_value,
'FOOandBARandBAZ': foo_value and bar_value and baz_value,
})
+ def test_depends_getattr(self):
+ with self.moz_configure('''
+ @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
+ def namespace(**kwargs):
+ return ReadOnlyNamespace(**kwargs)
+
+ option('--foo', nargs=1, help='foo')
+ @depends('--foo')
+ def foo(value):
+ return value
+
+ option('--bar', nargs=1, help='bar')
+ @depends('--bar')
+ def bar(value):
+ return value or None
+
+ @depends(foo, bar)
+ def foobar(foo, bar):
+ return namespace(foo=foo, bar=bar)
+
+ set_config('FOO', foobar.foo)
+ set_config('BAR', foobar.bar)
+ set_config('BAZ', foobar.baz)
+ '''):
+ config = self.get_config()
+ self.assertEqual(config, {
+ 'FOO': NegativeOptionValue(),
+ })
+
+ config = self.get_config(['--foo=foo'])
+ self.assertEqual(config, {
+ 'FOO': PositiveOptionValue(('foo',)),
+ })
+
+ config = self.get_config(['--bar=bar'])
+ self.assertEqual(config, {
+ 'FOO': NegativeOptionValue(),
+ 'BAR': PositiveOptionValue(('bar',)),
+ })
+
+ config = self.get_config(['--foo=foo', '--bar=bar'])
+ self.assertEqual(config, {
+ 'FOO': PositiveOptionValue(('foo',)),
+ 'BAR': PositiveOptionValue(('bar',)),
+ })
+
if __name__ == '__main__':
main()