Bug 1296530 - Store DependsFunction information for the sandbox as class instances instead of tuples. r?chmanchester draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 13 Oct 2016 13:04:18 +0900
changeset 425030 eb1db14b38b0ace5694322434481850daf7743bd
parent 425029 1b5ed817d31af894e37f61761ecadf315b93e639
child 425031 b5cd447e7b0a129bc83f69d306ff4aa16e132991
push id32321
push userbmo:mh+mozilla@glandium.org
push dateFri, 14 Oct 2016 02:53:47 +0000
reviewerschmanchester
bugs1296530
milestone52.0a1
Bug 1296530 - Store DependsFunction information for the sandbox as class instances instead of tuples. r?chmanchester
build/moz.configure/util.configure
python/mozbuild/mozbuild/configure/__init__.py
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -353,17 +353,19 @@ never = dependable(False)
 # of a @depends function in a non-immediate manner.
 #   @depends('--option')
 #   def option(value)
 #       return namespace(foo=value)
 #   set_config('FOO', delayed_getattr(option, 'foo')
 @template
 @imports('__sandbox__')
 def delayed_getattr(func, key):
-    _, deps = __sandbox__._depends.get(func, (None, ()))
+    deps = __sandbox__._depends.get(func, ())
+    if deps:
+        deps = deps.dependencies
 
     def result(value, _=None):
         # The @depends function we're being passed may have returned
         # None, or an object that simply doesn't have the wanted key.
         # In that case, just return None.
         return getattr(value, key, None)
 
     # Automatically add a dependency on --help when the given @depends
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -44,16 +44,27 @@ class ConfigureError(Exception):
 
 class SandboxDependsFunction(object):
     '''Sandbox-visible representation of @depends functions.'''
     def __call__(self, *arg, **kwargs):
         raise ConfigureError('The `%s` function may not be called'
                              % self.__name__)
 
 
+class DependsFunction(object):
+    __slots__ = ('func', 'dependencies')
+    def __init__(self, func, dependencies):
+        self.func = func
+        self.dependencies = dependencies
+
+    @property
+    def name(self):
+        return self.func.__name__
+
+
 class SandboxedGlobal(dict):
     '''Identifiable dict type for use as function global'''
 
 
 def forbidden_import(*args, **kwargs):
     raise ImportError('Importing modules is forbidden')
 
 
@@ -110,18 +121,17 @@ class ConfigureSandbox(dict):
 
     def __init__(self, config, environ=os.environ, argv=sys.argv,
                  stdout=sys.stdout, stderr=sys.stderr, logger=None):
         dict.__setitem__(self, '__builtins__', self.BUILTINS)
 
         self._paths = []
         self._all_paths = set()
         self._templates = set()
-        # Store the real function and its dependencies, behind each
-        # DependsFunction generated from @depends.
+        # Associate SandboxDependsFunctions to DependsFunctions.
         self._depends = {}
         self._seen = set()
         # Store the @imports added to a given function.
         self._imports = {}
 
         self._options = OrderedDict()
         # Store raw option (as per command line or environment) for each Option
         self._raw_options = OrderedDict()
@@ -285,53 +295,52 @@ class ConfigureSandbox(dict):
             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, need_help_dependency=True):
         if isinstance(arg, SandboxDependsFunction):
             assert arg in self._depends
-            func, deps = self._depends[arg]
-            if need_help_dependency and self._help_option not in deps:
+            f = self._depends[arg]
+            if need_help_dependency and self._help_option not in f.dependencies:
                 raise ConfigureError("Missing @depends for `%s`: '--help'" %
-                                     func.__name__)
+                                     f.name)
             return self._value_for(arg)
         return arg
 
     def _value_for(self, obj):
         if isinstance(obj, SandboxDependsFunction):
             return self._value_for_depends(obj)
 
         elif isinstance(obj, Option):
             return self._value_for_option(obj)
 
         assert False
 
     @memoize
     def _value_for_depends(self, obj):
         assert obj in self._depends
-        func, dependencies = self._depends[obj]
-        assert not inspect.isgeneratorfunction(func)
-        with_help = self._help_option in dependencies
+        f = self._depends[obj]
+        assert not inspect.isgeneratorfunction(f.func)
+        with_help = self._help_option in f.dependencies
         if with_help:
-            for arg in dependencies:
+            for arg in f.dependencies:
                 if isinstance(arg, SandboxDependsFunction):
-                    _, deps = self._depends[arg]
-                    if self._help_option not in deps:
+                    if self._help_option not in self._depends[arg].dependencies:
                         raise ConfigureError(
                             "`%s` depends on '--help' and `%s`. "
                             "`%s` must depend on '--help'"
-                            % (func.__name__, arg.__name__, arg.__name__))
+                            % (f.name, arg.__name__, arg.__name__))
         elif self._help:
             raise ConfigureError("Missing @depends for `%s`: '--help'" %
-                                 func.__name__)
+                                 f.name)
 
-        resolved_args = [self._value_for(d) for d in dependencies]
-        return func(*resolved_args)
+        resolved_args = [self._value_for(d) for d in f.dependencies]
+        return f.func(*resolved_args)
 
     @memoize
     def _value_for_option(self, option):
         implied = {}
         for implied_option in self._implied_options[:]:
             if implied_option.name not in (option.name, option.env):
                 continue
             self._implied_options.remove(implied_option)
@@ -444,17 +453,17 @@ class ConfigureSandbox(dict):
         dependencies = tuple(dependencies)
 
         def decorator(func):
             if inspect.isgeneratorfunction(func):
                 raise ConfigureError(
                     'Cannot decorate generator functions with @depends')
             func, glob = self._prepare_function(func)
             dummy = wraps(func)(SandboxDependsFunction())
-            self._depends[dummy] = func, dependencies
+            self._depends[dummy] = DependsFunction(func, dependencies)
 
             # 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(dummy)
             elif not self._help:
                 self._execution_queue.append((self._value_for, (dummy,)))
@@ -682,17 +691,17 @@ class ConfigureSandbox(dict):
 
         The `reason` argument indicates what caused the option to be implied.
         It is necessary when it cannot be inferred from the `value`.
         '''
         # Don't do anything when --help was on the command line
         if self._help:
             return
         if not reason and isinstance(value, SandboxDependsFunction):
-            deps = self._depends[value][1]
+            deps = self._depends[value].dependencies
             possible_reasons = [d for d in deps if d != self._help_option]
             if len(possible_reasons) == 1:
                 if isinstance(possible_reasons[0], Option):
                     reason = possible_reasons[0]
         if not reason and (isinstance(value, (bool, tuple)) or
                            isinstance(value, types.StringTypes)):
             # A reason can be provided automatically when imply_option
             # is called with an immediate value.