Bug 1296530 - Add a `when` argument to option(). r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 12 Oct 2016 17:45:53 +0900
changeset 425037 a542279b7c654e698237a0f6093f4917b6e09748
parent 425036 499adad7b025e63760f2ac19208efac6a2c481a7
child 425038 7c76e28bb8a26928168910d86f283ac518c5aa4a
push id32321
push userbmo:mh+mozilla@glandium.org
push dateFri, 14 Oct 2016 02:53:47 +0000
reviewerschmanchester
bugs1296530
milestone52.0a1
Bug 1296530 - Add a `when` argument to option(). r?chmanchester
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozbuild/test/configure/test_configure.py
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -319,23 +319,24 @@ class ConfigureSandbox(dict):
         return super(ConfigureSandbox, self).__setitem__(key, value)
 
     def _resolve(self, arg, need_help_dependency=True):
         if isinstance(arg, SandboxDependsFunction):
             return self._value_for_depends(self._depends[arg],
                                            need_help_dependency)
         return arg
 
-    def _value_for(self, obj):
+    def _value_for(self, obj, need_help_dependency=False):
         if isinstance(obj, SandboxDependsFunction):
             assert obj in self._depends
-            return self._value_for_depends(self._depends[obj])
+            return self._value_for_depends(self._depends[obj],
+                                           need_help_dependency)
 
         elif isinstance(obj, DependsFunction):
-            return self._value_for_depends(obj)
+            return self._value_for_depends(obj, need_help_dependency)
 
         elif isinstance(obj, Option):
             return self._value_for_option(obj)
 
         assert False
 
     @memoize
     def _value_for_depends(self, obj, need_help_dependency=False):
@@ -402,16 +403,22 @@ class ConfigureSandbox(dict):
                 reason = reason.split('=', 1)[0]
             raise InvalidOptionError(
                 "'%s' implied by '%s' conflicts with '%s' from the %s"
                 % (e.arg, reason, e.old_arg, e.old_origin))
 
         if option_string:
             self._raw_options[option] = option_string
 
+        when = self._conditions.get(option)
+        if (when and not self._value_for(when, need_help_dependency=True) and
+            value is not None and value.origin != 'default'):
+            raise InvalidOptionError(
+                '%s is not available in this configuration' % option_string)
+
         return value
 
     def _dependency(self, arg, callee_name, arg_name=None):
         if isinstance(arg, types.StringTypes):
             prefix, name, values = Option.split_option(arg)
             if values != ():
                 raise ConfigureError("Option must not contain an '='")
             if name not in self._options:
@@ -434,29 +441,36 @@ class ConfigureSandbox(dict):
         '''Implementation of option()
         This function creates and returns an Option() object, passing it the
         resolved arguments (uses the result of functions when functions are
         passed). In most cases, the result of this function is not expected to
         be used.
         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')
         args = [self._resolve(arg) for arg in args]
-        kwargs = {k: self._resolve(v) for k, v in kwargs.iteritems()}
+        kwargs = {k: self._resolve(v) for k, v in kwargs.iteritems()
+                                      if k != 'when'}
         option = Option(*args, **kwargs)
+        if when:
+            self._conditions[option] = when
         if option.name in self._options:
             raise ConfigureError('Option `%s` already defined' % option.option)
         if option.env in self._options:
             raise ConfigureError('Option `%s` already defined' % option.env)
         if option.name:
             self._options[option.name] = option
         if option.env:
             self._options[option.env] = option
 
-        if self._help:
+        if self._help and (when is None or
+                           self._value_for(when, need_help_dependency=True)):
             self._help.add(option)
 
         return option
 
     def depends_impl(self, *args, **kwargs):
         '''Implementation of @depends()
         This function is a decorator. It returns a function that subsequently
         takes a function and returns a dummy function. The dummy function
@@ -484,16 +498,26 @@ class ConfigureSandbox(dict):
                     % k)
 
         when = kwargs.get('when')
         if when is not None:
             when = self._dependency(when, '@depends', 'when')
 
         dependencies = tuple(self._dependency(arg, '@depends') for arg in args)
 
+        conditions = [
+            self._conditions[d]
+            for d in dependencies
+            if d in self._conditions and isinstance(d, Option)
+        ]
+        for c in conditions:
+            if c != when:
+                raise ConfigureError('@depends function needs the same `when` '
+                                     'as options it depends on')
+
         def decorator(func):
             if inspect.isgeneratorfunction(func):
                 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
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -696,16 +696,119 @@ class TestConfigure(unittest.TestCase):
             '''):
                 self.get_config()
 
         self.assertEquals(
             e.exception.message,
             'Option `--with-foo` already defined'
         )
 
+    def test_option_when(self):
+        with self.moz_configure('''
+            @depends('--help')
+            def always(_):
+                return True
+            @depends('--help')
+            def never(_):
+                return False
+            option('--with-foo', help='foo', when=always)
+            option('--with-bar', help='bar', when=never)
+            option('--with-qux', help='qux', when='--with-foo')
+
+            set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
+            set_config('BAR', depends('--with-bar', when=never)(lambda x: x))
+            set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x))
+        '''):
+            config = self.get_config()
+            self.assertEquals(config, {
+                'FOO': NegativeOptionValue(),
+            })
+
+            config = self.get_config(['--with-foo'])
+            self.assertEquals(config, {
+                'FOO': PositiveOptionValue(),
+                'QUX': NegativeOptionValue(),
+            })
+
+            config = self.get_config(['--with-foo', '--with-qux'])
+            self.assertEquals(config, {
+                'FOO': PositiveOptionValue(),
+                'QUX': PositiveOptionValue(),
+            })
+
+            with self.assertRaises(InvalidOptionError) as e:
+                self.get_config(['--with-bar'])
+
+            self.assertEquals(
+                e.exception.message,
+                '--with-bar is not available in this configuration'
+            )
+
+            with self.assertRaises(InvalidOptionError) as e:
+                self.get_config(['--with-qux'])
+
+            self.assertEquals(
+                e.exception.message,
+                '--with-qux is not available in this configuration'
+            )
+
+            help, config = self.get_config(['--help'])
+            self.assertEquals(help, textwrap.dedent('''\
+                Usage: configure [options]
+
+                Options: [defaults in brackets after descriptions]
+                  --help                    print this message
+                  --with-foo                foo
+
+                Environment variables:
+            '''))
+
+            help, config = self.get_config(['--help', '--with-foo'])
+            self.assertEquals(help, textwrap.dedent('''\
+                Usage: configure [options]
+
+                Options: [defaults in brackets after descriptions]
+                  --help                    print this message
+                  --with-foo                foo
+                  --with-qux                qux
+
+                Environment variables:
+            '''))
+
+        with self.moz_configure('''
+            @depends('--help')
+            def always(_):
+                return True
+            option('--with-foo', help='foo', when=always)
+            set_config('FOO', depends('--with-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('''
+            @depends('--help')
+            def always(_):
+                return True
+            @depends('--help')
+            def always2(_):
+                return True
+            option('--with-foo', help='foo', when=always)
+            set_config('FOO', depends('--with-foo', when=always2)(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')
+
     def test_include_failures(self):
         with self.assertRaises(ConfigureError) as e:
             with self.moz_configure('include("../foo.configure")'):
                 self.get_config()
 
         self.assertEquals(
             e.exception.message,
             'Cannot include `%s` because it is not in a subdirectory of `%s`'