| #!/usr/bin/python2.4 |
| # Copyright 2008, Google Inc. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Defer tool for SCons.""" |
| |
| |
| import os |
| import sys |
| import types |
| import SCons.Errors |
| |
| |
| # Current group name being executed by ExecuteDefer(). Set to None outside |
| # of ExecuteDefer(). |
| _execute_defer_context = None |
| |
| |
| class DeferGroup: |
| """Named list of functions to be deferred.""" |
| # If we derive DeferGroup from object, instances of it return type |
| # <class 'defer.DeferGroup'>, which prevents SCons.Util.semi_deepcopy() |
| # from calling its __semi_deepcopy__ function. |
| # TODO(sgk): Make semi_deepcopy() capable of handling classes derived from |
| # object. |
| |
| def __init__(self): |
| """Initialize deferred function object.""" |
| self.func_env_cwd = [] |
| self.after = set() |
| |
| def __semi_deepcopy__(self): |
| """Makes a semi-deep-copy of this object. |
| |
| Returns: |
| A semi-deep-copy of this object. |
| |
| This means it copies the sets and lists contained by this object, but |
| doesn't make copies of the function pointers and environments pointed to by |
| those lists. |
| |
| Needed so env.Clone() makes a copy of the defer list, so that functions |
| and after-relationships subsequently added to the clone are not added to |
| the parent. |
| """ |
| c = DeferGroup() |
| c.func_env_cwd = self.func_env_cwd[:] |
| c.after = self.after.copy() |
| return c |
| |
| |
| def SetDeferRoot(self): |
| """Sets the current environment as the root environment for defer. |
| |
| Args: |
| self: Current environment context. |
| |
| Functions deferred by environments cloned from the root environment (that is, |
| function deferred by children of the root environment) will be executed when |
| ExecuteDefer() is called from the root environment. |
| |
| Functions deferred by environments from which the root environment was cloned |
| (that is, functions deferred by parents of the root environment) will be |
| passed the root environment instead of the original parent environment. |
| (Otherwise, they would have no way to determine the root environment.) |
| """ |
| # Set the current environment as the root for holding defer groups |
| self['_DEFER_ROOT_ENV'] = self |
| |
| # Deferred functions this environment got from its parents will be run in the |
| # new root context. |
| for group in GetDeferGroups(self).values(): |
| new_list = [(func, self, cwd) for (func, env, cwd) in group.func_env_cwd] |
| group.func_env_cwd = new_list |
| |
| |
| def GetDeferRoot(self): |
| """Returns the root environment for defer. |
| |
| Args: |
| self: Current environment context. |
| |
| Returns: |
| The root environment for defer. If one of this environment's parents |
| called SetDeferRoot(), returns that environment. Otherwise returns the |
| current environment. |
| """ |
| return self.get('_DEFER_ROOT_ENV', self) |
| |
| |
| def GetDeferGroups(env): |
| """Returns the dict of defer groups from the root defer environment. |
| |
| Args: |
| env: Environment context. |
| |
| Returns: |
| The dict of defer groups from the root defer environment. |
| """ |
| return env.GetDeferRoot()['_DEFER_GROUPS'] |
| |
| |
| def ExecuteDefer(self): |
| """Executes deferred functions. |
| |
| Args: |
| self: Current environment context. |
| """ |
| # Check for re-entrancy |
| global _execute_defer_context |
| if _execute_defer_context: |
| raise SCons.Errors.UserError('Re-entrant call to ExecuteDefer().') |
| |
| # Save directory, so SConscript functions can occur in the right subdirs |
| oldcwd = os.getcwd() |
| |
| # If defer root is set and isn't this environment, we're being called from a |
| # sub-environment. That's not where we should be called. |
| if self.GetDeferRoot() != self: |
| print ('Warning: Ignoring call to ExecuteDefer() from child of the ' |
| 'environment passed to SetDeferRoot().') |
| return |
| |
| # Get list of defer groups from ourselves. |
| defer_groups = GetDeferGroups(self) |
| |
| # Loop through deferred functions |
| try: |
| while defer_groups: |
| did_work = False |
| for name, group in defer_groups.items(): |
| if group.after.intersection(defer_groups.keys()): |
| continue # Still have dependencies |
| |
| # Set defer context |
| _execute_defer_context = name |
| |
| # Remove this group from the list of defer groups now, in case one of |
| # the functions it calls adds back a function into that defer group. |
| del defer_groups[name] |
| |
| if group.func_env_cwd: |
| # Run all the functions in our named group |
| for func, env, cwd in group.func_env_cwd: |
| os.chdir(cwd) |
| func(env) |
| |
| # The defer groups have been altered, so restart the search for |
| # functions that can be executed. |
| did_work = True |
| break |
| |
| if not did_work: |
| errmsg = 'Error in ExecuteDefer: dependency cycle detected.\n' |
| for name, group in defer_groups.items(): |
| errmsg += ' %s after: %s\n' % (name, group.after) |
| raise SCons.Errors.UserError(errmsg) |
| finally: |
| # No longer in a defer context |
| _execute_defer_context = None |
| |
| # Restore directory |
| os.chdir(oldcwd) |
| |
| |
| def PrintDefer(self, print_functions=True): |
| """Prints the current defer dependency graph. |
| |
| Args: |
| self: Environment in which PrintDefer() was called. |
| print_functions: Print individual functions in defer groups. |
| """ |
| # Get the defer dict |
| # Get list of defer groups from ourselves. |
| defer_groups = GetDeferGroups(self) |
| dgkeys = defer_groups.keys() |
| dgkeys.sort() |
| for k in dgkeys: |
| print ' +- %s' % k |
| group = defer_groups[k] |
| after = list(group.after) |
| if after: |
| print ' | after' |
| after.sort() |
| for a in after: |
| print ' | +- %s' % a |
| if print_functions and group.func_env_cwd: |
| print ' functions' |
| for func, env, cwd in group.func_env_cwd: |
| print ' | +- %s %s' % (func.__name__, cwd) |
| |
| |
| def Defer(self, *args, **kwargs): |
| """Adds a deferred function or modifies defer dependencies. |
| |
| Args: |
| self: Environment in which Defer() was called |
| args: Positional arguments |
| kwargs: Named arguments |
| |
| The deferred function will be passed the environment used to call Defer(), |
| and will be executed in the same working directory as the calling SConscript. |
| (Exception: if this environment is cloned and the clone calls SetDeferRoot() |
| and then ExecuteDefer(), the function will be passed the root environment, |
| instead of the environment used to call Defer().) |
| |
| All deferred functions run after all SConscripts. Additional dependencies |
| may be specified with the after= keyword. |
| |
| Usage: |
| |
| env.Defer(func) |
| # Defer func() until after all SConscripts |
| |
| env.Defer(func, after=otherfunc) |
| # Defer func() until otherfunc() runs |
| |
| env.Defer(func, 'bob') |
| # Defer func() until after SConscripts, put in group 'bob' |
| |
| env.Defer(func2, after='bob') |
| # Defer func2() until after all funcs in 'bob' group have run |
| |
| env.Defer(func3, 'sam') |
| # Defer func3() until after SConscripts, put in group 'sam' |
| |
| env.Defer('bob', after='sam') |
| # Defer all functions in group 'bob' until after all functions in group |
| # 'sam' have run. |
| |
| env.Defer(func4, after=['bob', 'sam']) |
| # Defer func4() until after all functions in groups 'bob' and 'sam' have |
| # run. |
| """ |
| # Get name of group to defer and/or the a function |
| name = None |
| func = None |
| for a in args: |
| if isinstance(a, str): |
| name = a |
| elif isinstance(a, types.FunctionType): |
| func = a |
| if func and not name: |
| name = func.__name__ |
| |
| # TODO(rspangler): Why not allow multiple functions? Should be ok |
| |
| # Get list of names and/or functions this function should defer until after |
| after = [] |
| for a in self.Flatten(kwargs.get('after')): |
| if isinstance(a, str): |
| # TODO(rspangler): Should check if '$' in a, and if so, subst() it and |
| # recurse into it. |
| after.append(a) |
| elif isinstance(a, types.FunctionType): |
| after.append(a.__name__) |
| elif a is not None: |
| # Deferring |
| raise ValueError('Defer after=%r is not a function or name' % a) |
| |
| # Find the deferred function |
| defer_groups = GetDeferGroups(self) |
| if name not in defer_groups: |
| defer_groups[name] = DeferGroup() |
| group = defer_groups[name] |
| |
| # If we were given a function, also save environment and current directory |
| if func: |
| group.func_env_cwd.append((func, self, os.getcwd())) |
| |
| # Add dependencies for the function |
| group.after.update(after) |
| |
| # If we are already inside a call to ExecuteDefer(), any functions which are |
| # deferring until after the current function must also be deferred until |
| # after this new function. In short, this means that if b() defers until |
| # after a() and a() calls Defer() to defer c(), then b() must also defer |
| # until after c(). |
| if _execute_defer_context and name != _execute_defer_context: |
| for other_name, other_group in GetDeferGroups(self).items(): |
| if other_name == name: |
| continue # Don't defer after ourselves |
| if _execute_defer_context in other_group.after: |
| other_group.after.add(name) |
| |
| |
| def generate(env): |
| # NOTE: SCons requires the use of this name, which fails gpylint. |
| """SCons entry point for this tool.""" |
| env.Append(_DEFER_GROUPS={}) |
| |
| env.AddMethod(Defer) |
| env.AddMethod(ExecuteDefer) |
| env.AddMethod(GetDeferRoot) |
| env.AddMethod(PrintDefer) |
| env.AddMethod(SetDeferRoot) |