mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 12:50:09 +00:00
Bug 1296530 - Add an only_when context manager, and a when
argument to include(). r=chmanchester
--HG-- extra : rebase_source : a74c539148e0a0597d1ff4af85ccf917cc37e1d4
This commit is contained in:
parent
b527447baf
commit
5983667dd8
@ -31,6 +31,7 @@ from mozbuild.configure.util import (
|
||||
from mozbuild.util import (
|
||||
exec_,
|
||||
memoize,
|
||||
memoized_property,
|
||||
ReadOnlyDict,
|
||||
ReadOnlyNamespace,
|
||||
)
|
||||
@ -50,14 +51,25 @@ class SandboxDependsFunction(object):
|
||||
|
||||
|
||||
class DependsFunction(object):
|
||||
__slots__ = ('func', 'dependencies', 'sandboxed')
|
||||
def __init__(self, sandbox, func, dependencies):
|
||||
__slots__ = (
|
||||
'func', 'dependencies', 'when', 'sandboxed', 'sandbox', '_result')
|
||||
|
||||
def __init__(self, sandbox, func, dependencies, when=None):
|
||||
assert isinstance(sandbox, ConfigureSandbox)
|
||||
self.func = func
|
||||
self.dependencies = dependencies
|
||||
self.sandboxed = wraps(func)(SandboxDependsFunction())
|
||||
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)
|
||||
elif not sandbox._help:
|
||||
sandbox._execution_queue.append((sandbox._value_for, (self,)))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.func.__name__
|
||||
@ -69,6 +81,14 @@ class DependsFunction(object):
|
||||
for d in self.dependencies
|
||||
]
|
||||
|
||||
@memoized_property
|
||||
def result(self):
|
||||
if self.when and not self.sandbox._value_for(self.when):
|
||||
return None
|
||||
|
||||
resolved_args = [self.sandbox._value_for(d) for d in self.dependencies]
|
||||
return self.func(*resolved_args)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s.%s %s(%s)>' % (
|
||||
self.__class__.__module__,
|
||||
@ -78,6 +98,50 @@ class DependsFunction(object):
|
||||
)
|
||||
|
||||
|
||||
class CombinedDependsFunction(DependsFunction):
|
||||
def __init__(self, sandbox, func, dependencies):
|
||||
@memoize
|
||||
@wraps(func)
|
||||
def wrapper(*args):
|
||||
return func(args)
|
||||
|
||||
flatten_deps = []
|
||||
for d in dependencies:
|
||||
if isinstance(d, CombinedDependsFunction) and d.func == wrapper:
|
||||
for d2 in d.dependencies:
|
||||
if d2 not in flatten_deps:
|
||||
flatten_deps.append(d2)
|
||||
elif d not in flatten_deps:
|
||||
flatten_deps.append(d)
|
||||
|
||||
# Automatically add a --help dependency if one of the dependencies
|
||||
# depends on it.
|
||||
for d in flatten_deps:
|
||||
if (isinstance(d, DependsFunction) and
|
||||
sandbox._help_option in d.dependencies):
|
||||
flatten_deps.insert(0, sandbox._help_option)
|
||||
break
|
||||
|
||||
super(CombinedDependsFunction, self).__init__(
|
||||
sandbox, wrapper, flatten_deps)
|
||||
|
||||
@memoized_property
|
||||
def result(self):
|
||||
# Ignore --help for the combined result
|
||||
deps = self.dependencies
|
||||
if deps[0] == self.sandbox._help_option:
|
||||
deps = deps[1:]
|
||||
resolved_args = [self.sandbox._value_for(d) for d in deps]
|
||||
return self.func(*resolved_args)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, self.__class__) and
|
||||
self.func == other.func and
|
||||
set(self.dependencies) == set(other.dependencies))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
class SandboxedGlobal(dict):
|
||||
'''Identifiable dict type for use as function global'''
|
||||
|
||||
@ -91,7 +155,7 @@ class ConfigureSandbox(dict):
|
||||
This is a different kind of sandboxing than the one used for moz.build
|
||||
processing.
|
||||
|
||||
The sandbox has 8 primitives:
|
||||
The sandbox has 9 primitives:
|
||||
- option
|
||||
- depends
|
||||
- template
|
||||
@ -100,9 +164,11 @@ class ConfigureSandbox(dict):
|
||||
- set_config
|
||||
- set_define
|
||||
- imply_option
|
||||
- only_when
|
||||
|
||||
`option`, `include`, `set_config`, `set_define` and `imply_option` are
|
||||
functions. `depends`, `template`, and `imports` are decorators.
|
||||
functions. `depends`, `template`, and `imports` are decorators. `only_when`
|
||||
is a context_manager.
|
||||
|
||||
These primitives are declared as name_impl methods to this class and
|
||||
the mapping name -> name_impl is done automatically in __getitem__.
|
||||
@ -165,9 +231,12 @@ class ConfigureSandbox(dict):
|
||||
# Queue of functions to execute, with their arguments
|
||||
self._execution_queue = []
|
||||
|
||||
# Store the `when`s associated to some options/depends.
|
||||
# Store the `when`s associated to some options.
|
||||
self._conditions = {}
|
||||
|
||||
# A list of conditions to apply as a default `when` for every *_impl()
|
||||
self._default_conditions = []
|
||||
|
||||
self._helper = CommandLineHelper(environ, argv)
|
||||
|
||||
assert isinstance(config, dict)
|
||||
@ -353,16 +422,7 @@ class ConfigureSandbox(dict):
|
||||
elif self._help or need_help_dependency:
|
||||
raise ConfigureError("Missing @depends for `%s`: '--help'" %
|
||||
obj.name)
|
||||
return self._value_for_depends_real(obj)
|
||||
|
||||
@memoize
|
||||
def _value_for_depends_real(self, obj):
|
||||
when = self._conditions.get(obj)
|
||||
if when and not self._value_for(when):
|
||||
return None
|
||||
|
||||
resolved_args = [self._value_for(d) for d in obj.dependencies]
|
||||
return obj.func(*resolved_args)
|
||||
return obj.result
|
||||
|
||||
@memoize
|
||||
def _value_for_option(self, option):
|
||||
@ -441,6 +501,35 @@ class ConfigureSandbox(dict):
|
||||
callee_name))
|
||||
return arg
|
||||
|
||||
def _normalize_when(self, when, callee_name):
|
||||
if when is not None:
|
||||
when = self._dependency(when, callee_name, 'when')
|
||||
|
||||
if self._default_conditions:
|
||||
# Create a pseudo @depends function for the combination of all
|
||||
# default conditions and `when`.
|
||||
dependencies = [when] if when else []
|
||||
dependencies.extend(self._default_conditions)
|
||||
if len(dependencies) == 1:
|
||||
return dependencies[0]
|
||||
return CombinedDependsFunction(self, all, dependencies)
|
||||
return when
|
||||
|
||||
@contextmanager
|
||||
def only_when_impl(self, when):
|
||||
'''Implementation of only_when()
|
||||
|
||||
`only_when` is a context manager that essentially makes calls to
|
||||
other sandbox functions within the context block ignored.
|
||||
'''
|
||||
when = self._normalize_when(when, 'only_when')
|
||||
if when and self._default_conditions[-1:] != [when]:
|
||||
self._default_conditions.append(when)
|
||||
yield
|
||||
self._default_conditions.pop()
|
||||
else:
|
||||
yield
|
||||
|
||||
def option_impl(self, *args, **kwargs):
|
||||
'''Implementation of option()
|
||||
This function creates and returns an Option() object, passing it the
|
||||
@ -450,9 +539,7 @@ class ConfigureSandbox(dict):
|
||||
Command line argument/environment variable parsing for this Option is
|
||||
handled here.
|
||||
'''
|
||||
when = kwargs.get('when')
|
||||
if when is not None:
|
||||
when = self._dependency(when, 'option', 'when')
|
||||
when = self._normalize_when(kwargs.get('when'), 'option')
|
||||
args = [self._resolve(arg) for arg in args]
|
||||
kwargs = {k: self._resolve(v) for k, v in kwargs.iteritems()
|
||||
if k != 'when'}
|
||||
@ -501,9 +588,7 @@ class ConfigureSandbox(dict):
|
||||
"depends_impl() got an unexpected keyword argument '%s'"
|
||||
% k)
|
||||
|
||||
when = kwargs.get('when')
|
||||
if when is not None:
|
||||
when = self._dependency(when, '@depends', 'when')
|
||||
when = self._normalize_when(kwargs.get('when'), '@depends')
|
||||
|
||||
dependencies = tuple(self._dependency(arg, '@depends') for arg in args)
|
||||
|
||||
@ -522,34 +607,24 @@ class ConfigureSandbox(dict):
|
||||
raise ConfigureError(
|
||||
'Cannot decorate generator functions with @depends')
|
||||
func, glob = self._prepare_function(func)
|
||||
depends = DependsFunction(self, func, dependencies)
|
||||
if when:
|
||||
self._conditions[depends] = when
|
||||
|
||||
# Only @depends functions with a dependency on '--help' are
|
||||
# executed immediately. Everything else is queued for later
|
||||
# execution.
|
||||
if self._help_option in dependencies:
|
||||
self._value_for(depends)
|
||||
elif not self._help:
|
||||
self._execution_queue.append((self._value_for, (depends,)))
|
||||
|
||||
depends = DependsFunction(self, func, dependencies, when=when)
|
||||
return depends.sandboxed
|
||||
|
||||
return decorator
|
||||
|
||||
def include_impl(self, what):
|
||||
def include_impl(self, what, when=None):
|
||||
'''Implementation of include().
|
||||
Allows to include external files for execution in the sandbox.
|
||||
It is possible to use a @depends function as argument, in which case
|
||||
the result of the function is the file name to include. This latter
|
||||
feature is only really meant for --enable-application/--enable-project.
|
||||
'''
|
||||
what = self._resolve(what)
|
||||
if what:
|
||||
if not isinstance(what, types.StringTypes):
|
||||
raise TypeError("Unexpected type: '%s'" % type(what).__name__)
|
||||
self.include_file(what)
|
||||
with self.only_when_impl(when):
|
||||
what = self._resolve(what)
|
||||
if what:
|
||||
if not isinstance(what, types.StringTypes):
|
||||
raise TypeError("Unexpected type: '%s'" % type(what).__name__)
|
||||
self.include_file(what)
|
||||
|
||||
def template_impl(self, func):
|
||||
'''Implementation of @template.
|
||||
@ -701,8 +776,7 @@ class ConfigureSandbox(dict):
|
||||
in which case the result from these functions is used. If the result
|
||||
of either function is None, the configuration item is not set.
|
||||
'''
|
||||
if when is not None:
|
||||
when = self._dependency(when, 'set_config', 'when')
|
||||
when = self._normalize_when(when, 'set_config')
|
||||
|
||||
self._execution_queue.append((
|
||||
self._resolve_and_set, (self._config, name, value, when)))
|
||||
@ -715,8 +789,7 @@ class ConfigureSandbox(dict):
|
||||
is None, the define is not set. If the result is False, the define is
|
||||
explicitly undefined (-U).
|
||||
'''
|
||||
if when is not None:
|
||||
when = self._dependency(when, 'set_define', 'when')
|
||||
when = self._normalize_when(when, 'set_define')
|
||||
|
||||
defines = self._config.setdefault('DEFINES', {})
|
||||
self._execution_queue.append((
|
||||
@ -788,8 +861,7 @@ class ConfigureSandbox(dict):
|
||||
"the `imply_option` call."
|
||||
% option)
|
||||
|
||||
if when is not None:
|
||||
when = self._dependency(when, 'imply_option', 'when')
|
||||
when = self._normalize_when(when, 'imply_option')
|
||||
|
||||
prefix, name, values = Option.split_option(option)
|
||||
if values != ():
|
||||
|
@ -902,6 +902,78 @@ class TestConfigure(unittest.TestCase):
|
||||
|
||||
self.assertEquals(e.exception.message, "Unexpected type: 'int'")
|
||||
|
||||
def test_include_when(self):
|
||||
with MockedOpen({
|
||||
os.path.join(test_data_path, 'moz.configure'): textwrap.dedent('''
|
||||
@depends('--help')
|
||||
def always(_):
|
||||
return True
|
||||
@depends('--help')
|
||||
def never(_):
|
||||
return False
|
||||
|
||||
option('--with-foo', help='foo')
|
||||
|
||||
include('always.configure', when=always)
|
||||
include('never.configure', when=never)
|
||||
include('foo.configure', when='--with-foo')
|
||||
|
||||
set_config('FOO', foo)
|
||||
set_config('BAR', bar)
|
||||
set_config('QUX', qux)
|
||||
'''),
|
||||
os.path.join(test_data_path, 'always.configure'): textwrap.dedent('''
|
||||
option('--with-bar', help='bar')
|
||||
@depends('--with-bar')
|
||||
def bar(x):
|
||||
if x:
|
||||
return 'bar'
|
||||
'''),
|
||||
os.path.join(test_data_path, 'never.configure'): textwrap.dedent('''
|
||||
option('--with-qux', help='qux')
|
||||
@depends('--with-qux')
|
||||
def qux(x):
|
||||
if x:
|
||||
return 'qux'
|
||||
'''),
|
||||
os.path.join(test_data_path, 'foo.configure'): textwrap.dedent('''
|
||||
option('--with-foo-really', help='really foo')
|
||||
@depends('--with-foo-really')
|
||||
def foo(x):
|
||||
if x:
|
||||
return 'foo'
|
||||
|
||||
include('foo2.configure', when='--with-foo-really')
|
||||
'''),
|
||||
os.path.join(test_data_path, 'foo2.configure'): textwrap.dedent('''
|
||||
set_config('FOO2', True)
|
||||
'''),
|
||||
}):
|
||||
config = self.get_config()
|
||||
self.assertEquals(config, {})
|
||||
|
||||
config = self.get_config(['--with-foo'])
|
||||
self.assertEquals(config, {})
|
||||
|
||||
config = self.get_config(['--with-bar'])
|
||||
self.assertEquals(config, {
|
||||
'BAR': 'bar',
|
||||
})
|
||||
|
||||
with self.assertRaises(InvalidOptionError) as e:
|
||||
self.get_config(['--with-qux'])
|
||||
|
||||
self.assertEquals(
|
||||
e.exception.message,
|
||||
'--with-qux is not available in this configuration'
|
||||
)
|
||||
|
||||
config = self.get_config(['--with-foo', '--with-foo-really'])
|
||||
self.assertEquals(config, {
|
||||
'FOO': 'foo',
|
||||
'FOO2': True,
|
||||
})
|
||||
|
||||
def test_sandbox_failures(self):
|
||||
with self.assertRaises(KeyError) as e:
|
||||
with self.moz_configure('''
|
||||
@ -1147,6 +1219,95 @@ class TestConfigure(unittest.TestCase):
|
||||
self.assertEquals(e.exception.message,
|
||||
"Invalid argument to @imports: 'os*'")
|
||||
|
||||
def test_only_when(self):
|
||||
moz_configure = '''
|
||||
option('--enable-when', help='when')
|
||||
@depends('--enable-when', '--help')
|
||||
def when(value, _):
|
||||
return bool(value)
|
||||
|
||||
with only_when(when):
|
||||
option('--foo', nargs='*', help='foo')
|
||||
@depends('--foo')
|
||||
def foo(value):
|
||||
return value
|
||||
|
||||
set_config('FOO', foo)
|
||||
set_define('FOO', foo)
|
||||
|
||||
# It is possible to depend on a function defined in a only_when
|
||||
# block. It then resolves to `None`.
|
||||
set_config('BAR', depends(foo)(lambda x: x))
|
||||
set_define('BAR', depends(foo)(lambda x: x))
|
||||
'''
|
||||
|
||||
with self.moz_configure(moz_configure):
|
||||
config = self.get_config()
|
||||
self.assertEqual(config, {
|
||||
'DEFINES': {},
|
||||
})
|
||||
|
||||
config = self.get_config(['--enable-when'])
|
||||
self.assertEqual(config, {
|
||||
'BAR': NegativeOptionValue(),
|
||||
'FOO': NegativeOptionValue(),
|
||||
'DEFINES': {
|
||||
'BAR': NegativeOptionValue(),
|
||||
'FOO': NegativeOptionValue(),
|
||||
},
|
||||
})
|
||||
|
||||
config = self.get_config(['--enable-when', '--foo=bar'])
|
||||
self.assertEqual(config, {
|
||||
'BAR': PositiveOptionValue(['bar']),
|
||||
'FOO': PositiveOptionValue(['bar']),
|
||||
'DEFINES': {
|
||||
'BAR': PositiveOptionValue(['bar']),
|
||||
'FOO': PositiveOptionValue(['bar']),
|
||||
},
|
||||
})
|
||||
|
||||
# The --foo option doesn't exist when --enable-when is not given.
|
||||
with self.assertRaises(InvalidOptionError) as e:
|
||||
self.get_config(['--foo'])
|
||||
|
||||
self.assertEquals(e.exception.message,
|
||||
'--foo is not available in this configuration')
|
||||
|
||||
# Cannot depend on an option defined in a only_when block, because we
|
||||
# don't know what OptionValue would make sense.
|
||||
with self.moz_configure(moz_configure + '''
|
||||
set_config('QUX', depends('--foo')(lambda x: x))
|
||||
'''):
|
||||
with self.assertRaises(ConfigureError) as e:
|
||||
self.get_config()
|
||||
|
||||
self.assertEquals(e.exception.message,
|
||||
'@depends function needs the same `when` as '
|
||||
'options it depends on')
|
||||
|
||||
with self.moz_configure(moz_configure + '''
|
||||
set_config('QUX', depends('--foo', when=when)(lambda x: x))
|
||||
'''):
|
||||
self.get_config(['--enable-when'])
|
||||
|
||||
# Using imply_option for an option defined in a only_when block fails
|
||||
# similarly if the imply_option happens outside the block.
|
||||
with self.moz_configure('''
|
||||
imply_option('--foo', True)
|
||||
''' + moz_configure):
|
||||
with self.assertRaises(InvalidOptionError) as e:
|
||||
self.get_config()
|
||||
|
||||
self.assertEquals(e.exception.message,
|
||||
'--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'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user