Bug 1257823 - Move imply_option() to the global scope
Like set_config and set_define, we move imply_option to the global
scope.
Note: similarly again, the move is split in 3 parts. While for the other
2, the intermediate steps still work, in this case, it's possible the
intermediate steps are semi broken, with error handling not working as
expected. The unit tests pass, so I'd say it's fine, considering the
code that I think is semi broken goes away in 2 commits. Plus, the next
2 commits will be folded with this one...
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -9,18 +9,20 @@ import os
import sys
import types
from collections import OrderedDict
from functools import wraps
from mozbuild.configure.options import (
CommandLineHelper,
ConflictingOptionError,
InvalidOptionError,
+ NegativeOptionValue,
Option,
OptionValue,
+ PositiveOptionValue,
)
from mozbuild.configure.help import HelpFormatter
from mozbuild.util import (
ReadOnlyDict,
ReadOnlyNamespace,
)
import mozpack.path as mozpath
@@ -46,17 +48,17 @@ class DependsOutput(object):
def __init__(self):
super(DependsOutput, self).__init__()
self.implied_options = []
def imply_option(self, option, reason=None):
if not isinstance(option, types.StringTypes):
raise TypeError('imply_option must be given a string')
- self.implied_options.append((option, reason))
+ self.implied_options.append((option, inspect.stack()[1], reason))
def forbidden_import(*args, **kwargs):
raise ImportError('Importing modules is forbidden')
class ConfigureSandbox(dict):
"""Represents a sandbox for executing Python code for build configuration.
@@ -125,17 +127,17 @@ class ConfigureSandbox(dict):
self._results = {}
# Store values for each Option, as per returned by Option.get_value
self._option_values = {}
# Store raw option (as per command line or environment) for each Option
self._raw_options = {}
# Store options added with `imply_option`, and the reason they were
# added (which can either have been given to `imply_option`, or
- # infered.
+ # inferred.
self._implied_options = {}
# Store all results from _prepare_function
self._prepared_functions = set()
self._helper = CommandLineHelper(environ, argv)
assert isinstance(config, dict)
@@ -180,21 +182,20 @@ class ConfigureSandbox(dict):
'''Executes the given file within the sandbox, and ensure the overall
consistency of the executed script.'''
self.exec_file(path)
# All command line arguments should have been removed (handled) by now.
for arg in self._helper:
without_value = arg.split('=', 1)[0]
if arg in self._implied_options:
- func, reason = self._implied_options[arg]
+ frameinfo, reason = self._implied_options[arg]
raise ConfigureError(
- '`%s`, emitted by `%s` in `%s`, was not handled.'
- % (without_value, func.__name__,
- func.func_code.co_filename))
+ '`%s`, emitted from `%s` line `%d`, was not handled.'
+ % (without_value, frameinfo[1], frameinfo[2]))
raise InvalidOptionError('Unknown option: %s' % without_value)
# All options must be referenced by some @depends function
for option in self._options.itervalues():
if option not in self._seen:
raise ConfigureError(
'Option `%s` is not handled ; reference it with a @depends'
% option.option
@@ -257,17 +258,17 @@ class ConfigureSandbox(dict):
if option.name:
self._options[option.name] = option
if option.env:
self._options[option.env] = option
try:
value, option_string = self._helper.handle(option)
except ConflictingOptionError as e:
- func, reason = self._implied_options[e.arg]
+ frameinfo, reason = self._implied_options[e.arg]
raise InvalidOptionError(
"'%s' implied by '%s' conflicts with '%s' from the %s"
% (e.arg, reason, e.old_arg, e.old_origin))
if self._help:
self._help.add(option)
self._option_values[option] = value
@@ -349,34 +350,34 @@ class ConfigureSandbox(dict):
"`%s` must depend on '--help'"
% (func.__name__, arg.__name__, arg.__name__))
if self._help and not with_help:
return dummy
self._results[func] = func(*resolved_args)
- for option, reason in result.implied_options:
+ for option, frameinfo, reason in result.implied_options:
self._helper.add(option, 'implied')
if not reason:
deps = []
for arg in dependencies:
if not isinstance(arg, Option):
raise ConfigureError(
"Cannot infer what implied '%s'" % option)
if arg == self._help_option:
continue
deps.append(self._raw_options.get(arg) or
self.arg.option)
if len(deps) != 1:
raise ConfigureError(
"Cannot infer what implied '%s'" % option)
reason = deps[0]
- self._implied_options[option] = func, reason
+ self._implied_options[option] = frameinfo, reason
return dummy
return decorator
def include_impl(self, what):
'''Implementation of include().
Allows to include external files for execution in the sandbox.
@@ -451,16 +452,81 @@ class ConfigureSandbox(dict):
`value` can be references to @depends functions, in which case the
result from these functions is used. If the result of such functions
is None, the define is not set. If the result is False, the define is
explicitly undefined (-U).
'''
defines = self._config.setdefault('DEFINES', {})
self._resolve_and_set(defines, name, value)
+ def imply_option_impl(self, option, value, reason=None):
+ '''Implementation of imply_option().
+ Injects additional options as if they had been passed on the command
+ line. The `option` argument is a string as in option()'s `name` or
+ `env`. The option must be declared after `imply_option` references it.
+ The `value` argument indicates the value to pass to the option.
+ It can be True (the positive option is injected*), False (the negative
+ option is injected*), None (the option is not injected), a string or a
+ tuple (the positive option is injected with the given values).
+ The `value` argument can also be (and usually) is a reference to a
+ @depends function, in which case the result of that function will be
+ used as per the descripted mapping above.
+ The `reason` argument indicates what caused the option to be implied.
+ It is necessary when it cannot be inferred from the `value`.
+
+ * the positive option is --enable-foo/--with-foo, the negative option
+ is --disable-foo/--without-foo.
+ imply_option('--enable-foo', True)
+ imply_option('--disable-foo', True)
+ are both equivalent to `--enable-foo` on the command line.
+
+ imply_option('--enable-foo', False)
+ imply_option('--disable-foo', False)
+ are both equivalent to `--disable-foo` on the command line.
+
+ imply_option('--enable-foo', None)
+ imply_option('--disable-foo', None)
+ are both equivalent to not passing any flag on the command line.
+
+ It is recommended to use the positive form ('--enable' or '--with') for
+ `option`.
+ '''
+ if not reason and isinstance(value, DummyFunction):
+ deps = self._depends[value][1]
+ possible_reasons = [d for d in deps if d != self._help_option]
+ if len(possible_reasons) == 1:
+ if isinstance(possible_reasons[0], Option):
+ reason = (self._raw_options.get(possible_reasons[0]) or
+ possible_reasons[0].option)
+
+ if not reason or not isinstance(value, DummyFunction):
+ raise ConfigureError(
+ "Cannot infer what implies '%s'. Please add a `reason` to "
+ "the `imply_option` call."
+ % option)
+
+ value = self._resolve(value, need_help_dependency=False)
+ if value is not None:
+ if isinstance(value, OptionValue):
+ pass
+ elif value is True:
+ value = PositiveOptionValue()
+ elif value is False or value == ():
+ value = NegativeOptionValue()
+ elif isinstance(value, types.StringTypes):
+ value = PositiveOptionValue((value,))
+ elif isinstance(value, tuple):
+ value = PositiveOptionValue(value)
+ else:
+ raise TypeError("Unexpected type: '%s'" % type(value))
+
+ option = value.format(option)
+ self._helper.add(option, 'implied')
+ self._implied_options[option] = inspect.stack()[1], reason
+
def _prepare_function(self, func):
'''Alter the given function global namespace with the common ground
for @depends, @template and @advanced.
'''
if not inspect.isfunction(func):
raise TypeError("Unexpected type: '%s'" % type(func))
if func in self._prepared_functions:
return func, func.func_globals
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option('--enable-foo', help='enable foo')
+
+@depends('--enable-foo', '--help')
+def foo(value, help):
+ if value:
+ return True
+
+imply_option('--enable-bar', foo)
+
+
+option('--enable-bar', help='enable bar')
+
+@depends('--enable-bar')
+def bar(value):
+ if value:
+ return value
+
+set_config('BAR', bar)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
@@ -0,0 +1,31 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option('--enable-hoge', help='enable hoge')
+
+@depends('--enable-hoge')
+def hoge(value):
+ return value
+
+
+option('--enable-foo', help='enable foo')
+
+@depends('--enable-foo', hoge, '--help')
+def foo(value, help):
+ if value:
+ return True
+
+imply_option('--enable-bar', foo)
+
+
+option('--enable-bar', help='enable bar')
+
+@depends('--enable-bar')
+def bar(value):
+ if value:
+ return value
+
+set_config('BAR', bar)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
@@ -0,0 +1,34 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option('--enable-foo', help='enable foo')
+
+@depends('--enable-foo')
+def foo(value):
+ if value:
+ return False
+
+imply_option('--enable-bar', foo)
+
+
+option('--disable-hoge', help='enable hoge')
+
+@depends('--disable-hoge')
+def hoge(value):
+ if not value:
+ return False
+
+imply_option('--enable-bar', hoge)
+
+
+option('--enable-bar', default=True, help='enable bar')
+
+@depends('--enable-bar')
+def bar(value):
+ if not value:
+ return value
+
+set_config('BAR', bar)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option('--enable-foo', help='enable foo')
+
+@depends('--enable-foo')
+def foo(value):
+ if value:
+ return True
+
+imply_option('--enable-bar', foo)
+
+
+option('--enable-bar', help='enable bar')
+
+@depends('--enable-bar')
+def bar(value):
+ if value:
+ return value
+
+set_config('BAR', bar)
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option('--enable-foo', nargs='*', help='enable foo')
+
+@depends('--enable-foo')
+def foo(value):
+ if value:
+ return value
+
+imply_option('--enable-bar', foo)
+
+
+option('--enable-bar', nargs='*', help='enable bar')
+
+@depends('--enable-bar')
+def bar(value):
+ if value:
+ return value
+
+set_config('BAR', bar)
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -1,16 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
from StringIO import StringIO
import sys
+import traceback
import unittest
from mozunit import main
from mozbuild.configure.options import (
InvalidOptionError,
NegativeOptionValue,
PositiveOptionValue,
@@ -371,11 +372,103 @@ class TestConfigure(unittest.TestCase):
self.assertEquals(config['DEFINES'], {'BAR': False})
with self.assertRaises(ConfigureError):
# Both --set-foo and --set-name=FOO are going to try to
# set_define('FOO'...)
self.get_config(['--set-foo', '--set-name=FOO'],
configure='set_define.configure')
+ def test_imply_option_simple(self):
+ config = self.get_config([], configure='imply_option/simple.configure')
+ self.assertEquals(config, {})
+
+ config = self.get_config(['--enable-foo'],
+ configure='imply_option/simple.configure')
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(['--enable-foo', '--disable-bar'],
+ configure='imply_option/simple.configure')
+
+ self.assertEquals(
+ e.exception.message,
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line")
+
+ def test_imply_option_negative(self):
+ config = self.get_config([],
+ configure='imply_option/negative.configure')
+ self.assertEquals(config, {})
+
+ config = self.get_config(['--enable-foo'],
+ configure='imply_option/negative.configure')
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(
+ ['--enable-foo', '--enable-bar'],
+ configure='imply_option/negative.configure')
+
+ self.assertEquals(
+ e.exception.message,
+ "'--disable-bar' implied by '--enable-foo' conflicts with "
+ "'--enable-bar' from the command-line")
+
+ config = self.get_config(['--disable-hoge'],
+ configure='imply_option/negative.configure')
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], NegativeOptionValue())
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(
+ ['--disable-hoge', '--enable-bar'],
+ configure='imply_option/negative.configure')
+
+ self.assertEquals(
+ e.exception.message,
+ "'--disable-bar' implied by '--disable-hoge' conflicts with "
+ "'--enable-bar' from the command-line")
+
+ def test_imply_option_values(self):
+ config = self.get_config([], configure='imply_option/values.configure')
+ self.assertEquals(config, {})
+
+ config = self.get_config(['--enable-foo=a'],
+ configure='imply_option/values.configure')
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue(('a',)))
+
+ config = self.get_config(['--enable-foo=a,b'],
+ configure='imply_option/values.configure')
+ self.assertIn('BAR', config)
+ self.assertEquals(config['BAR'], PositiveOptionValue(('a','b')))
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(['--enable-foo=a,b', '--disable-bar'],
+ configure='imply_option/values.configure')
+
+ self.assertEquals(
+ e.exception.message,
+ "'--enable-bar=a,b' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line")
+
+ def test_imply_option_infer(self):
+ config = self.get_config([], configure='imply_option/infer.configure')
+
+ with self.assertRaises(InvalidOptionError) as e:
+ config = self.get_config(['--enable-foo', '--disable-bar'],
+ configure='imply_option/infer.configure')
+
+ self.assertEquals(
+ e.exception.message,
+ "'--enable-bar' implied by '--enable-foo' conflicts with "
+ "'--disable-bar' from the command-line")
+
+
+ with self.assertRaises(ConfigureError) as e:
+ self.get_config([], configure='imply_option/infer_ko.configure')
+
if __name__ == '__main__':
main()