Bug 1257823 - Move set_config() to the global scope draft
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 22 Mar 2016 14:21:32 +0900
changeset 343862 14f8bd79e14906469b3aee92c1b94cd069796607
parent 343861 0e6907fd8f6c716fb47afd0cf7a03829a8c874cf
child 343863 767341c1e29822fbfd63c7a54e3b38abd452e1ba
push id13691
push userbmo:mh+mozilla@glandium.org
push dateWed, 23 Mar 2016 10:00:34 +0000
bugs1257823
milestone48.0a1
Bug 1257823 - Move set_config() to the global scope The way set_config is set currently makes it difficult to introspect moz.configure files to know what configuration items are being set, because they're hidden in the control flow of functions. This makes some of the moz.configure more convoluted, but this is why there are templates, and we can improve the recurring cases afterwards. Note: this is only the part where a global set_config() is added. this is not how this would land. This would land folded with the next two commits. This was split to make the review easier. In this whole series, we can see some emerging patterns, I'm ignoring them on purpose here. We can deal with templating them in followups, cf. paragraph above.
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozbuild/test/configure/data/set_config.configure
python/mozbuild/mozbuild/test/configure/test_configure.py
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -217,23 +217,23 @@ class ConfigureSandbox(dict):
 
         if (not isinstance(value, DummyFunction) and
                 value not in self._templates):
             raise KeyError('Cannot assign `%s` because it is neither a '
                            '@depends nor a @template' % key)
 
         return super(ConfigureSandbox, self).__setitem__(key, value)
 
-    def _resolve(self, arg):
+    def _resolve(self, arg, need_help_dependency=True):
         if isinstance(arg, DummyFunction):
             assert arg in self._depends
             func = self._depends[arg]
             assert not inspect.isgeneratorfunction(func)
             assert func in self._results
-            if not func.with_help:
+            if need_help_dependency and not func.with_help:
                 raise ConfigureError("Missing @depends for `%s`: '--help'" %
                                      func.__name__)
             result = self._results[func]
             return result
         return arg
 
     def option_impl(self, *args, **kwargs):
         '''Implementation of option()
@@ -410,29 +410,53 @@ class ConfigureSandbox(dict):
         Templates allow to simplify repetitive constructs, or to implement
         helper decorators and somesuch.
         '''
         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,
         )
         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.
         '''
         func, glob = self._prepare_function(func)
         glob.update(__builtins__=__builtins__)
         return func
 
+    def set_config_impl(self, name, value):
+        '''Implementation of set_config().
+        Sets a configuration items with the given name to the given value.
+        Both `name` and `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 configuration item is not set.
+        '''
+        # Don't set anything when --help was on the command line
+        if self._help:
+            return
+        name = self._resolve(name, need_help_dependency=False)
+        if name is None:
+            return
+        if not isinstance(name, types.StringTypes):
+            raise TypeError("Unexpected type: '%s'" % type(name))
+        if name in self._config:
+            raise ConfigureError(
+                "Cannot add '%s' to configuration: Key already "
+                "exists" % name)
+        value = self._resolve(value, need_help_dependency=False)
+        if value is not None:
+            self._config[name] = value
+
     def _set_define(self, name, value):
         defines = self._config.setdefault('DEFINES', {})
         if name in defines:
             raise ConfigureError("'%s' is already defined" % name)
         defines[name] = value
 
     def _prepare_function(self, func):
         '''Alter the given function global namespace with the common ground
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/configure/data/set_config.configure
@@ -0,0 +1,43 @@
+# -*- 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('--set-foo', help='set foo')
+
+@depends('--set-foo')
+def foo(value):
+    if value:
+        return True
+
+set_config('FOO', foo)
+
+
+option('--set-bar', help='set bar')
+
+@depends('--set-bar')
+def bar(value):
+    return bool(value)
+
+set_config('BAR', bar)
+
+
+option('--set-value', nargs=1, help='set value')
+
+@depends('--set-value')
+def set_value(value):
+    if value:
+        return value[0]
+
+set_config('VALUE', set_value)
+
+
+option('--set-name', nargs=1, help='set name')
+
+@depends('--set-name')
+def set_name(value):
+    if value:
+        return value[0]
+
+set_config(set_name, True)
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -10,36 +10,40 @@ import unittest
 
 from mozunit import main
 
 from mozbuild.configure.options import (
     InvalidOptionError,
     NegativeOptionValue,
     PositiveOptionValue,
 )
-from mozbuild.configure import ConfigureSandbox
+from mozbuild.configure import (
+    ConfigureError,
+    ConfigureSandbox,
+)
 
 import mozpack.path as mozpath
 
 test_data_path = mozpath.abspath(mozpath.dirname(__file__))
 test_data_path = mozpath.join(test_data_path, 'data')
 
 
 class TestConfigure(unittest.TestCase):
-    def get_result(self, args=[], environ={}, prog='/bin/configure'):
+    def get_result(self, args=[], environ={}, configure='moz.configure',
+                   prog='/bin/configure'):
         config = {}
         out = StringIO()
         sandbox = ConfigureSandbox(config, environ, [prog] + args, out, out)
 
-        sandbox.run(mozpath.join(test_data_path, 'moz.configure'))
+        sandbox.run(mozpath.join(test_data_path, configure))
 
         return config, out.getvalue()
 
-    def get_config(self, options=[], env={}):
-        config, out = self.get_result(options, environ=env)
+    def get_config(self, options=[], env={}, **kwargs):
+        config, out = self.get_result(options, environ=env, **kwargs)
         self.assertEquals('', out)
         return config
 
     def test_defaults(self):
         config = self.get_config()
         self.maxDiff = None
         self.assertEquals({
             'CHOICES': NegativeOptionValue(),
@@ -297,11 +301,46 @@ class TestConfigure(unittest.TestCase):
         self.assertIn('TEMPLATE_VALUE_2', config)
         self.assertEquals(config['TEMPLATE_VALUE_2'], 21)
 
     def test_template_advanced(self):
         config = self.get_config(['--enable-advanced-template'])
         self.assertIn('PLATFORM', config)
         self.assertEquals(config['PLATFORM'], sys.platform)
 
+    def test_set_config(self):
+        config, out = self.get_result(['--help'],
+                                      configure='set_config.configure')
+        self.assertEquals(config, {})
+
+        config = self.get_config(['--set-foo'],
+                                 configure='set_config.configure')
+        self.assertIn('FOO', config)
+        self.assertEquals(config['FOO'], True)
+
+        config = self.get_config(['--set-bar'],
+                                 configure='set_config.configure')
+        self.assertNotIn('FOO', config)
+        self.assertIn('BAR', config)
+        self.assertEquals(config['BAR'], True)
+
+        config = self.get_config(['--set-value=qux'],
+                                 configure='set_config.configure')
+        self.assertIn('VALUE', config)
+        self.assertEquals(config['VALUE'], 'qux')
+
+        config = self.get_config(['--set-name=hoge'],
+                                 configure='set_config.configure')
+        self.assertIn('hoge', config)
+        self.assertEquals(config['hoge'], True)
+
+        config = self.get_config([], configure='set_config.configure')
+        self.assertEquals(config, {'BAR': False})
+
+        with self.assertRaises(ConfigureError):
+            # Both --set-foo and --set-name=FOO are going to try to
+            # set_config('FOO'...)
+            self.get_config(['--set-foo', '--set-name=FOO'],
+                            configure='set_config.configure')
+
 
 if __name__ == '__main__':
     main()