Bug 1257823 - Move imply_option() to the global scope. r=nalexander
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 23 Mar 2016 14:18:57 +0900
changeset 328030 06dc23858ed715e62229c2f80147af28b0416fff
parent 328029 407e18a1a0241fb62f0391d698ff9954625ad06b
child 328031 76d58b17343e47d057bbbc22634e0ff3e537b8b5
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1257823
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1257823 - Move imply_option() to the global scope. r=nalexander Like set_config and set_define, we move imply_option to the global scope.
build/moz.configure/init.configure
js/moz.configure
moz.configure
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozbuild/test/configure/data/imply_option/infer.configure
python/mozbuild/mozbuild/test/configure/data/imply_option/infer_ko.configure
python/mozbuild/mozbuild/test/configure/data/imply_option/negative.configure
python/mozbuild/mozbuild/test/configure/data/imply_option/simple.configure
python/mozbuild/mozbuild/test/configure/data/imply_option/values.configure
python/mozbuild/mozbuild/test/configure/data/moz.configure
python/mozbuild/mozbuild/test/configure/test_configure.py
toolkit/moz.configure
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -594,17 +594,19 @@ set_define('XP_LINUX', target_is_linux)
 # The application/project to build
 # ==============================================================
 option('--enable-application', nargs=1, env='MOZ_BUILD_APP',
        help='Application to build. Same as --enable-project.')
 
 @depends('--enable-application', '--help')
 def application(app, help):
     if app:
-        imply_option(app.format('--enable-project'))
+        return app
+
+imply_option('--enable-project', application)
 
 
 @depends(check_build_environment, '--help')
 def default_project(build_env, help):
     if build_env.topobjdir.endswith('/js/src'):
         return 'js'
     return 'browser'
 
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -118,49 +118,52 @@ js_option('--enable-instruments', env='M
 
 @depends('--enable-instruments', target)
 def instruments(value, target):
     if value and target.os != 'OSX':
         error('--enable-instruments cannot be used when targeting %s'
               % target.os)
     if value:
         add_old_configure_assignment('MOZ_INSTRUMENTS', True)
-        imply_option('--enable-profiling', reason='--enable-instruments')
         return True
 
 set_config('MOZ_INSTRUMENTS', instruments)
 set_define('MOZ_INSTRUMENTS', instruments)
+imply_option('--enable-profiling', instruments, reason='--enable-instruments')
 
 js_option('--enable-callgrind', env='MOZ_CALLGRIND',
           help='Enable callgrind profiling')
 
 @depends('--enable-callgrind')
 def callgrind(value):
     if value:
-        imply_option('--enable-profiling')
         return True
 
 set_define('MOZ_CALLGRIND', callgrind)
+imply_option('--enable-profiling', callgrind)
 
 js_option('--enable-profiling', env='MOZ_PROFILING',
           help='Set compile flags necessary for using sampling profilers '
                '(e.g. shark, perf)')
 
-@depends('--enable-profiling', target)
-def profiling(value, target):
+@depends('--enable-profiling')
+def profiling(value):
     if value:
         add_old_configure_assignment('MOZ_PROFILING', True)
+        return True
 
-        if target.kernel == 'WINNT' or (target.kernel == 'Linux' and
-                                        target.os == 'GNU'):
-            imply_option('--enable-vtune', reason='--enable-profiling')
+@depends(profiling, target)
+def imply_vtune(value, target):
+    if value and (target.kernel == 'WINNT' or (target.kernel == 'Linux' and
+                                               target.os == 'GNU')):
         return True
 
 set_config('MOZ_PROFILING', profiling)
 set_define('MOZ_PROFILING', profiling)
+imply_option('--enable-vtune', imply_vtune, reason='--enable-profiling')
 
 
 js_option('--enable-vtune', env='MOZ_VTUNE', help='Enable vtune profiling')
 
 @depends('--enable-vtune')
 def vtune(value):
     if value:
         return True
--- a/moz.configure
+++ b/moz.configure
@@ -27,21 +27,26 @@ set_define('E10S_TESTING_ONLY', e10s_tes
 
 
 option('--enable-artifact-builds', env='MOZ_ARTIFACT_BUILDS',
        help='Download and use prebuilt binary artifacts.')
 
 @depends('--enable-artifact-builds')
 def artifact_builds(value):
     if value:
-        imply_option('--disable-compile-environment')
         return True
 
 set_config('MOZ_ARTIFACT_BUILDS', artifact_builds)
 
+@depends('--enable-artifact-builds')
+def imply_disable_compile_environment(value):
+    if value:
+        return False
+
+imply_option('--enable-compile-environment', imply_disable_compile_environment)
 
 option('--disable-compile-environment',
        help='Disable compiler/library checks')
 
 @depends('--disable-compile-environment')
 def compile_environment(value):
     if value:
         add_old_configure_assignment('COMPILE_ENVIRONMENT', True)
--- 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
 
@@ -35,30 +37,16 @@ class DummyFunction(object):
         raise RuntimeError('The `%s` function may not be called'
                            % self.__name__)
 
 
 class SandboxedGlobal(dict):
     '''Identifiable dict type for use as function global'''
 
 
-class DependsOutput(object):
-    '''Class for objects holding the options implied by a @depends function.'''
-    __slots__ = ('implied_options',)
-
-    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))
-
-
 def forbidden_import(*args, **kwargs):
     raise ImportError('Importing modules is forbidden')
 
 
 class ConfigureSandbox(dict):
     """Represents a sandbox for executing Python code for build configuration.
     This is a different kind of sandboxing than the one used for moz.build
     processing.
@@ -127,17 +115,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)
@@ -182,21 +170,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
@@ -259,17 +246,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
@@ -288,19 +275,17 @@ class ConfigureSandbox(dict):
         references. The decorated function is called as soon as the decorator
         is called, and the arguments it receives are the OptionValue or
         function results corresponding to each of the arguments to @depends.
         As an exception, when a HelpFormatter is attached, only functions that
         have '--help' in their @depends argument list are called.
 
         The decorated function is altered to use a different global namespace
         for its execution. This different global namespace exposes a limited
-        set of functions from os.path, and one additional functions:
-        `imply_option`. It allows to inject additional options as if they had
-        been passed on the command line.
+        set of functions from os.path.
         '''
         if not args:
             raise ConfigureError('@depends needs at least one argument')
 
         resolved_args = []
         dependencies = []
         for arg in args:
             if isinstance(arg, types.StringTypes):
@@ -330,56 +315,31 @@ class ConfigureSandbox(dict):
             resolved_args.append(resolved_arg)
         dependencies = tuple(dependencies)
 
         def decorator(func):
             if inspect.isgeneratorfunction(func):
                 raise ConfigureError(
                     'Cannot decorate generator functions with @depends')
             func, glob = self._prepare_function(func)
-            result = DependsOutput()
-            glob.update(
-                imply_option=result.imply_option,
-            )
             dummy = wraps(func)(DummyFunction())
             self._depends[dummy] = func, dependencies
             with_help = self._help_option in dependencies
             if with_help:
                 for arg in args:
                     if isinstance(arg, DummyFunction):
                         _, deps = self._depends[arg]
                         if self._help_option not in deps:
                             raise ConfigureError(
                                 "`%s` depends on '--help' and `%s`. "
                                 "`%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:
-                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
+            if not self._help or with_help:
+                self._results[func] = func(*resolved_args)
 
             return dummy
 
         return decorator
 
     def include_impl(self, what):
         '''Implementation of include().
         Allows to include external files for execution in the sandbox.
@@ -404,16 +364,17 @@ class ConfigureSandbox(dict):
         '''
         template, glob = self._prepare_function(func)
         glob.update(
             advanced=self.advanced_impl,
             depends=self.depends_impl,
             option=self.option_impl,
             set_config=self.set_config_impl,
             set_define=self.set_define_impl,
+            imply_option=self.imply_option_impl,
         )
         self._templates.add(template)
         return template
 
     def advanced_impl(self, func):
         '''Implementation of @advanced.
         This function gives the decorated function access to the complete set
         of builtins, allowing the import keyword as an expected side effect.
@@ -454,16 +415,93 @@ class ConfigureSandbox(dict):
         `value` can be references to @depends functions, in which case the
         result from these functions is used. If the result of either function
         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. In this case `imply_option` injects the positive option
+          (--enable-foo/--with-foo).
+              imply_option('--enable-foo', True)
+              imply_option('--disable-foo', True)
+          are both equivalent to `--enable-foo` on the command line.
+
+        - False. In this case `imply_option` injects the negative option
+          (--disable-foo/--without-foo).
+              imply_option('--enable-foo', False)
+              imply_option('--disable-foo', False)
+          are both equivalent to `--disable-foo` on the command line.
+
+        - None. In this case `imply_option` does nothing.
+              imply_option('--enable-foo', None)
+              imply_option('--disable-foo', None)
+          are both equivalent to not passing any flag on the command line.
+
+        - a string or a tuple. In this case `imply_option` injects the positive
+          option with the given value(s).
+              imply_option('--enable-foo', 'a')
+              imply_option('--disable-foo', 'a')
+          are both equivalent to `--enable-foo=a` on the command line.
+              imply_option('--enable-foo', ('a', 'b'))
+              imply_option('--disable-foo', ('a', 'b'))
+          are both equivalent to `--enable-foo=a,b` on the command line.
+
+        Because imply_option('--disable-foo', ...) can be misleading, it is
+        recommended to use the positive form ('--enable' or '--with') for
+        `option`.
+
+        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`.
+        '''
+        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)
+def foo(value, hoge):
+    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/data/moz.configure
+++ b/python/mozbuild/mozbuild/test/configure/data/moz.configure
@@ -100,50 +100,16 @@ def simple(simple, help):
 option('--with-returned-default', default=simple, help='Returned default')
 
 @depends('--with-returned-default')
 def default(value):
     return value
 
 set_config('DEFAULTED', default)
 
-# @depends functions can also declare that some extra options are implied.
-# Those options need to be defined _after_ the function, and they mustn't
-# appear on the command line or the environment with conflicting values.
-@depends('--enable-values')
-def values(values):
-    if values:
-        imply_option('--enable-implied')
-        imply_option(values.format('--with-implied-values'))
-        imply_option(values.format('WITH_IMPLIED_ENV'))
-
-option('--enable-implied', help='Implied')
-
-option('--with-implied-values', nargs='*', help='Implied values')
-
-option(env='WITH_IMPLIED_ENV', nargs='*', help='Implied env')
-
-@depends('--enable-implied')
-def implied(value):
-    return value
-
-set_config('IMPLIED', implied)
-
-@depends('--with-implied-values')
-def implied(values):
-    return values
-
-set_config('IMPLIED_VALUES', implied)
-
-@depends('WITH_IMPLIED_ENV')
-def implied(values):
-    return values
-
-set_config('IMPLIED_ENV', implied)
-
 @depends('--enable-values', '--help')
 def choices(values, help):
     if len(values):
         return {
             'alpha': ('a', 'b', 'c'),
             'numeric': ('0', '1', '2'),
         }.get(values[0])
 
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -51,19 +51,16 @@ class TestConfigure(unittest.TestCase):
             'IS_GCC': NegativeOptionValue(),
             'REMAINDER': (PositiveOptionValue(), NegativeOptionValue(),
                           NegativeOptionValue(), NegativeOptionValue()),
             'SIMPLE': NegativeOptionValue(),
             'VALUES': NegativeOptionValue(),
             'VALUES2': NegativeOptionValue(),
             'VALUES3': NegativeOptionValue(),
             'WITH_ENV': NegativeOptionValue(),
-            'IMPLIED': NegativeOptionValue(),
-            'IMPLIED_ENV': NegativeOptionValue(),
-            'IMPLIED_VALUES': NegativeOptionValue(),
         }, config)
 
     def test_help(self):
         config, out = self.get_result(['--help'])
         self.assertEquals({}, config)
         self.maxDiff = None
         self.assertEquals(
             'Usage: configure [options]\n'
@@ -72,27 +69,24 @@ class TestConfigure(unittest.TestCase):
             '  --help                    print this message\n'
             '  --enable-simple           Enable simple\n'
             '  --enable-with-env         Enable with env\n'
             '  --enable-values           Enable values\n'
             '  --without-thing           Build without thing\n'
             '  --with-stuff              Build with stuff\n'
             '  --option                  Option\n'
             '  --with-returned-default   Returned default [not-simple]\n'
-            '  --enable-implied          Implied\n'
-            '  --with-implied-values     Implied values\n'
             '  --returned-choices        Choices\n'
             '  --enable-advanced-template\n'
             '                            Advanced template\n'
             '  --enable-include          Include\n'
             '  --with-advanced           Advanced\n'
             '\n'
             'Environment variables:\n'
-            '  CC                        C Compiler\n'
-            '  WITH_IMPLIED_ENV          Implied env\n',
+            '  CC                        C Compiler\n',
             out
         )
 
     def test_unknown(self):
         with self.assertRaises(InvalidOptionError):
             self.get_config(['--unknown'])
 
     def test_simple(self):
@@ -185,57 +179,16 @@ class TestConfigure(unittest.TestCase):
         self.assertEquals(
             PositiveOptionValue(('simple',)), config['DEFAULTED'])
 
         config = self.get_config(['--disable-simple'])
         self.assertIn('DEFAULTED', config)
         self.assertEquals(
             PositiveOptionValue(('not-simple',)), config['DEFAULTED'])
 
-    def test_implied_options(self):
-        config = self.get_config(['--enable-values'])
-        self.assertIn('IMPLIED', config)
-        self.assertIn('IMPLIED_VALUES', config)
-        self.assertIn('IMPLIED_ENV', config)
-        self.assertEquals(PositiveOptionValue(), config['IMPLIED'])
-        self.assertEquals(PositiveOptionValue(), config['IMPLIED_VALUES'])
-        self.assertEquals(PositiveOptionValue(), config['IMPLIED_ENV'])
-
-        config = self.get_config(['--enable-values=a'])
-        self.assertIn('IMPLIED', config)
-        self.assertIn('IMPLIED_VALUES', config)
-        self.assertIn('IMPLIED_ENV', config)
-        self.assertEquals(PositiveOptionValue(), config['IMPLIED'])
-        self.assertEquals(
-            PositiveOptionValue(('a',)), config['IMPLIED_VALUES'])
-        self.assertEquals(PositiveOptionValue(('a',)), config['IMPLIED_ENV'])
-
-        config = self.get_config(['--enable-values=a,b'])
-        self.assertIn('IMPLIED', config)
-        self.assertIn('IMPLIED_VALUES', config)
-        self.assertIn('IMPLIED_ENV', config)
-        self.assertEquals(PositiveOptionValue(), config['IMPLIED'])
-        self.assertEquals(
-            PositiveOptionValue(('a', 'b')), config['IMPLIED_VALUES'])
-        self.assertEquals(
-            PositiveOptionValue(('a', 'b')), config['IMPLIED_ENV'])
-
-        config = self.get_config(['--disable-values'])
-        self.assertIn('IMPLIED', config)
-        self.assertIn('IMPLIED_VALUES', config)
-        self.assertIn('IMPLIED_ENV', config)
-        self.assertEquals(NegativeOptionValue(), config['IMPLIED'])
-        self.assertEquals(NegativeOptionValue(), config['IMPLIED_VALUES'])
-        self.assertEquals(NegativeOptionValue(), config['IMPLIED_ENV'])
-
-        # --enable-values implies --enable-implied, which conflicts with
-        # --disable-implied
-        with self.assertRaises(InvalidOptionError):
-            self.get_config(['--enable-values', '--disable-implied'])
-
     def test_returned_choices(self):
         for val in ('a', 'b', 'c'):
             config = self.get_config(
                 ['--enable-values=alpha', '--returned-choices=%s' % val])
             self.assertIn('CHOICES', config)
             self.assertEquals(PositiveOptionValue((val,)), config['CHOICES'])
 
         for val in ('0', '1', '2'):
@@ -367,11 +320,107 @@ class TestConfigure(unittest.TestCase):
         config = get_config([])
         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'...)
             get_config(['--set-foo', '--set-name=FOO'])
 
+    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')
+
+        self.assertEquals(
+            e.exception.message,
+            "Cannot infer what implies '--enable-bar'. Please add a `reason` "
+            "to the `imply_option` call.")
+
 
 if __name__ == '__main__':
     main()
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -24,21 +24,21 @@ set_define('MOZ_USE_SYSTRACE', systrace)
 
 
 option('--enable-jprof', env='MOZ_JPROF',
        help='Enable jprof profiling tool (needs mozilla/tools/jprof)')
 
 @depends('--enable-jprof')
 def jprof(value):
     if value:
-        imply_option('--enable-profiling')
         return True
 
 set_config('MOZ_JPROF', jprof)
 set_define('MOZ_JPROF', jprof)
+imply_option('--enable-profiling', jprof)
 
 @depends(target)
 def sps_profiler(target):
     if target.os == 'Android':
         return target.cpu in ('arm', 'x86')
     elif target.kernel == 'Linux':
         return target.cpu in ('x86', 'x86_64')
     return target.os in ('OSX', 'WINNT')
@@ -55,21 +55,21 @@ set_define('MOZ_ENABLE_PROFILER_SPS', sp
 option('--enable-dmd', env='MOZ_DMD',
        help='Enable Dark Matter Detector (heap profiler). '
             'Also enables jemalloc, replace-malloc and profiling')
 
 @depends('--enable-dmd')
 def dmd(value):
     if value:
         add_old_configure_assignment('MOZ_DMD', True)
-        imply_option('--enable-profiling')
         return True
 
 set_config('MOZ_DMD', dmd)
 set_define('MOZ_DMD', dmd)
+imply_option('--enable-profiling', dmd)
 
 # Javascript engine
 # ==============================================================
 include('../js/moz.configure')
 
 
 # L10N
 # ==============================================================
@@ -302,21 +302,21 @@ option('--disable-ffmpeg',
        help='Disable FFmpeg for fragmented H264/AAC decoding')
 
 @depends('--disable-ffmpeg', target)
 def ffmpeg(value, target):
     enabled = bool(value)
     if value.origin == 'default':
         enabled = target.os not in ('Android', 'WINNT')
     if enabled:
-        imply_option('--enable-fmp4', '--enable-ffmpeg')
         return True
 
 set_config('MOZ_FFMPEG', ffmpeg)
 set_define('MOZ_FFMPEG', ffmpeg)
+imply_option('--enable-fmp4', ffmpeg, '--enable-ffmpeg')
 
 # Built-in fragmented MP4 support.
 # ==============================================================
 option('--disable-fmp4', env='MOZ_FMP4',
        help='Disable support for in built Fragmented MP4 parsing')
 
 @depends('--disable-fmp4', target, wmf, applemedia)
 def fmp4(value, target, wmf, applemedia):