diff options
Diffstat (limited to 'third_party/scons/scons-local/SCons/Executor.py')
-rw-r--r-- | third_party/scons/scons-local/SCons/Executor.py | 354 |
1 files changed, 296 insertions, 58 deletions
diff --git a/third_party/scons/scons-local/SCons/Executor.py b/third_party/scons/scons-local/SCons/Executor.py index a37da07..f49faaf 100644 --- a/third_party/scons/scons-local/SCons/Executor.py +++ b/third_party/scons/scons-local/SCons/Executor.py @@ -6,7 +6,7 @@ Nodes. """ # -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -28,15 +28,88 @@ Nodes. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # -__revision__ = "src/engine/SCons/Executor.py 3842 2008/12/20 22:59:52 scons" +__revision__ = "src/engine/SCons/Executor.py 3897 2009/01/13 06:45:54 scons" import string +import UserList from SCons.Debug import logInstanceCreation import SCons.Errors import SCons.Memoize +class Batch: + """Remembers exact association between targets + and sources of executor.""" + def __init__(self, targets=[], sources=[]): + self.targets = targets + self.sources = sources + + + +class TSList(UserList.UserList): + """A class that implements $TARGETS or $SOURCES expansions by wrapping + an executor Method. This class is used in the Executor.lvars() + to delay creation of NodeList objects until they're needed. + + Note that we subclass UserList.UserList purely so that the + is_Sequence() function will identify an object of this class as + a list during variable expansion. We're not really using any + UserList.UserList methods in practice. + """ + def __init__(self, func): + self.func = func + def __getattr__(self, attr): + nl = self.func() + return getattr(nl, attr) + def __getitem__(self, i): + nl = self.func() + return nl[i] + def __getslice__(self, i, j): + nl = self.func() + i = max(i, 0); j = max(j, 0) + return nl[i:j] + def __str__(self): + nl = self.func() + return str(nl) + def __repr__(self): + nl = self.func() + return repr(nl) + +class TSObject: + """A class that implements $TARGET or $SOURCE expansions by wrapping + an Executor method. + """ + def __init__(self, func): + self.func = func + def __getattr__(self, attr): + n = self.func() + return getattr(n, attr) + def __str__(self): + n = self.func() + if n: + return str(n) + return '' + def __repr__(self): + n = self.func() + if n: + return repr(n) + return '' + +def rfile(node): + """ + A function to return the results of a Node's rfile() method, + if it exists, and the Node itself otherwise (if it's a Value + Node, e.g.). + """ + try: + rfile = node.rfile + except AttributeError: + return node + else: + return rfile() + + class Executor: """A class for controlling instances of executing an action. @@ -58,12 +131,96 @@ class Executor: self.post_actions = [] self.env = env self.overridelist = overridelist - self.targets = targets - self.sources = sources[:] - self.sources_need_sorting = False + if targets or sources: + self.batches = [Batch(targets[:], sources[:])] + else: + self.batches = [] self.builder_kw = builder_kw self._memo = {} + def get_lvars(self): + try: + return self.lvars + except AttributeError: + self.lvars = { + 'CHANGED_SOURCES' : TSList(self._get_changed_sources), + 'CHANGED_TARGETS' : TSList(self._get_changed_targets), + 'SOURCE' : TSObject(self._get_source), + 'SOURCES' : TSList(self._get_sources), + 'TARGET' : TSObject(self._get_target), + 'TARGETS' : TSList(self._get_targets), + 'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources), + 'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets), + } + return self.lvars + + def _get_changes(self): + cs = [] + ct = [] + us = [] + ut = [] + for b in self.batches: + if b.targets[0].changed(): + cs.extend(map(rfile, b.sources)) + ct.extend(b.targets) + else: + us.extend(map(rfile, b.sources)) + ut.extend(b.targets) + self._changed_sources_list = SCons.Util.NodeList(cs) + self._changed_targets_list = SCons.Util.NodeList(ct) + self._unchanged_sources_list = SCons.Util.NodeList(us) + self._unchanged_targets_list = SCons.Util.NodeList(ut) + + def _get_changed_sources(self, *args, **kw): + try: + return self._changed_sources_list + except AttributeError: + self._get_changes() + return self._changed_sources_list + + def _get_changed_targets(self, *args, **kw): + try: + return self._changed_targets_list + except AttributeError: + self._get_changes() + return self._changed_targets_list + + def _get_source(self, *args, **kw): + #return SCons.Util.NodeList([rfile(self.batches[0].sources[0]).get_subst_proxy()]) + return rfile(self.batches[0].sources[0]).get_subst_proxy() + + def _get_sources(self, *args, **kw): + return SCons.Util.NodeList(map(lambda n: rfile(n).get_subst_proxy(), self.get_all_sources())) + + def _get_target(self, *args, **kw): + #return SCons.Util.NodeList([self.batches[0].targets[0].get_subst_proxy()]) + return self.batches[0].targets[0].get_subst_proxy() + + def _get_targets(self, *args, **kw): + return SCons.Util.NodeList(map(lambda n: n.get_subst_proxy(), self.get_all_targets())) + + def _get_unchanged_sources(self, *args, **kw): + try: + return self._unchanged_sources_list + except AttributeError: + self._get_changes() + return self._unchanged_sources_list + + def _get_unchanged_targets(self, *args, **kw): + try: + return self._unchanged_targets_list + except AttributeError: + self._get_changes() + return self._unchanged_targets_list + + def get_action_targets(self): + if not self.action_list: + return [] + targets_string = self.action_list[0].get_targets(self.env, self) + if targets_string[0] == '$': + targets_string = targets_string[1:] + return self.get_lvars()[targets_string] + def set_action_list(self, action): import SCons.Util if not SCons.Util.is_List(action): @@ -76,6 +233,58 @@ class Executor: def get_action_list(self): return self.pre_actions + self.action_list + self.post_actions + def get_all_targets(self): + """Returns all targets for all batches of this Executor.""" + result = [] + for batch in self.batches: + # TODO(1.5): remove the list() cast + result.extend(list(batch.targets)) + return result + + def get_all_sources(self): + """Returns all sources for all batches of this Executor.""" + result = [] + for batch in self.batches: + # TODO(1.5): remove the list() cast + result.extend(list(batch.sources)) + return result + + def get_all_children(self): + """Returns all unique children (dependencies) for all batches + of this Executor. + + The Taskmaster can recognize when it's already evaluated a + Node, so we don't have to make this list unique for its intended + canonical use case, but we expect there to be a lot of redundancy + (long lists of batched .cc files #including the same .h files + over and over), so removing the duplicates once up front should + save the Taskmaster a lot of work. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_all_targets(): + result.extend(target.children()) + return result + + def get_all_prerequisites(self): + """Returns all unique (order-only) prerequisites for all batches + of this Executor. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_all_targets(): + # TODO(1.5): remove the list() cast + result.extend(list(target.prerequisites)) + return result + + def get_action_side_effects(self): + + """Returns all side effects for all batches of this + Executor used by the underlying Action. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_action_targets(): + result.extend(target.side_effects) + return result + memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) def get_build_env(self): @@ -109,14 +318,17 @@ class Executor: """ env = self.get_build_env() try: - cwd = self.targets[0].cwd + cwd = self.batches[0].targets[0].cwd except (IndexError, AttributeError): cwd = None - return scanner.path(env, cwd, self.targets, self.get_sources()) + return scanner.path(env, cwd, + self.get_all_targets(), + self.get_all_sources()) def get_kw(self, kw={}): result = self.builder_kw.copy() result.update(kw) + result['executor'] = self return result def do_nothing(self, target, kw): @@ -128,7 +340,9 @@ class Executor: kw = self.get_kw(kw) status = 0 for act in self.get_action_list(): - status = apply(act, (self.targets, self.get_sources(), env), kw) + #args = (self.get_all_targets(), self.get_all_sources(), env) + args = ([], [], env) + status = apply(act, args, kw) if isinstance(status, SCons.Errors.BuildError): status.executor = self raise status @@ -136,7 +350,7 @@ class Executor: msg = "Error %s" % status raise SCons.Errors.BuildError( errstr=msg, - node=self.targets, + node=self.batches[0].targets, executor=self, action=act) return status @@ -155,24 +369,32 @@ class Executor: """Add source files to this Executor's list. This is necessary for "multi" Builders that can be called repeatedly to build up a source file list for a given target.""" - self.sources.extend(sources) - self.sources_need_sorting = True + # TODO(batch): extend to multiple batches + assert (len(self.batches) == 1) + # TODO(batch): remove duplicates? + sources = filter(lambda x, s=self.batches[0].sources: x not in s, sources) + self.batches[0].sources.extend(sources) def get_sources(self): - if self.sources_need_sorting: - self.sources = SCons.Util.uniquer_hashables(self.sources) - self.sources_need_sorting = False - return self.sources + return self.batches[0].sources + + def add_batch(self, targets, sources): + """Add pair of associated target and source to this Executor's list. + This is necessary for "batch" Builders that can be called repeatedly + to build up a list of matching target and source files that will be + used in order to update multiple target files at once from multiple + corresponding source files, for tools like MSVC that support it.""" + self.batches.append(Batch(targets, sources)) def prepare(self): """ Preparatory checks for whether this Executor can go ahead and (try to) build its targets. """ - for s in self.get_sources(): + for s in self.get_all_sources(): if s.missing(): msg = "Source `%s' not found, needed by target `%s'." - raise SCons.Errors.StopError, msg % (s, self.targets[0]) + raise SCons.Errors.StopError, msg % (s, self.batches[0].targets[0]) def add_pre_action(self, action): self.pre_actions.append(action) @@ -184,7 +406,7 @@ class Executor: def my_str(self): env = self.get_build_env() - get = lambda action, t=self.targets, s=self.get_sources(), e=env: \ + get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \ action.genstring(t, s, e) return string.join(map(get, self.get_action_list()), "\n") @@ -209,7 +431,7 @@ class Executor: except KeyError: pass env = self.get_build_env() - get = lambda action, t=self.targets, s=self.get_sources(), e=env: \ + get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \ action.get_contents(t, s, e) result = string.join(map(get, self.get_action_list()), "") self._memo['get_contents'] = result @@ -223,11 +445,13 @@ class Executor: return 0 def scan_targets(self, scanner): - self.scan(scanner, self.targets) + # TODO(batch): scan by batches + self.scan(scanner, self.get_all_targets()) def scan_sources(self, scanner): - if self.sources: - self.scan(scanner, self.get_sources()) + # TODO(batch): scan by batches + if self.batches[0].sources: + self.scan(scanner, self.get_all_sources()) def scan(self, scanner, node_list): """Scan a list of this Executor's files (targets or sources) for @@ -237,6 +461,7 @@ class Executor: """ env = self.get_build_env() + # TODO(batch): scan by batches) deps = [] if scanner: for node in node_list: @@ -261,16 +486,16 @@ class Executor: deps.extend(self.get_implicit_deps()) - for tgt in self.targets: + for tgt in self.get_all_targets(): tgt.add_to_implicit(deps) - def _get_unignored_sources_key(self, ignore=()): - return tuple(ignore) + def _get_unignored_sources_key(self, node, ignore=()): + return (node,) + tuple(ignore) memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key)) - def get_unignored_sources(self, ignore=()): - ignore = tuple(ignore) + def get_unignored_sources(self, node, ignore=()): + key = (node,) + tuple(ignore) try: memo_dict = self._memo['get_unignored_sources'] except KeyError: @@ -278,56 +503,56 @@ class Executor: self._memo['get_unignored_sources'] = memo_dict else: try: - return memo_dict[ignore] + return memo_dict[key] except KeyError: pass - sourcelist = self.get_sources() + if node: + # TODO: better way to do this (it's a linear search, + # but it may not be critical path)? + sourcelist = [] + for b in self.batches: + if node in b.targets: + sourcelist = b.sources + break + else: + sourcelist = self.get_all_sources() if ignore: idict = {} for i in ignore: idict[i] = 1 sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist) - memo_dict[ignore] = sourcelist + memo_dict[key] = sourcelist return sourcelist - def _process_sources_key(self, func, ignore=()): - return (func, tuple(ignore)) - - memoizer_counters.append(SCons.Memoize.CountDict('process_sources', _process_sources_key)) - - def process_sources(self, func, ignore=()): - memo_key = (func, tuple(ignore)) - try: - memo_dict = self._memo['process_sources'] - except KeyError: - memo_dict = {} - self._memo['process_sources'] = memo_dict - else: - try: - return memo_dict[memo_key] - except KeyError: - pass - - result = map(func, self.get_unignored_sources(ignore)) - - memo_dict[memo_key] = result - - return result - def get_implicit_deps(self): """Return the executor's implicit dependencies, i.e. the nodes of the commands to be executed.""" result = [] build_env = self.get_build_env() for act in self.get_action_list(): - result.extend(act.get_implicit_deps(self.targets, self.get_sources(), build_env)) + deps = act.get_implicit_deps(self.get_all_targets(), + self.get_all_sources(), + build_env) + result.extend(deps) return result + + +_batch_executors = {} + +def GetBatchExecutor(key): + return _batch_executors[key] + +def AddBatchExecutor(key, executor): + assert not _batch_executors.has_key(key) + _batch_executors[key] = executor + nullenv = None + def get_NullEnvironment(): """Use singleton pattern for Null Environments.""" global nullenv @@ -354,7 +579,7 @@ class Null: """ def __init__(self, *args, **kw): if __debug__: logInstanceCreation(self, 'Executor.Null') - self.targets = kw['targets'] + self.batches = [Batch(kw['targets'][:], [])] def get_build_env(self): return get_NullEnvironment() def get_build_scanner_path(self): @@ -365,17 +590,30 @@ class Null: pass def get_unignored_sources(self, *args, **kw): return tuple(()) + def get_action_targets(self): + return [] def get_action_list(self): return [] + def get_all_targets(self): + return self.batches[0].targets + def get_all_sources(self): + return self.batches[0].targets[0].sources + def get_all_children(self): + return self.get_all_sources() + def get_all_prerequisites(self): + return [] + def get_action_side_effects(self): + return [] def __call__(self, *args, **kw): return 0 def get_contents(self): return '' - def _morph(self): """Morph this Null executor to a real Executor object.""" + batches = self.batches self.__class__ = Executor - self.__init__([], targets=self.targets) + self.__init__([]) + self.batches = batches # The following methods require morphing this Null Executor to a # real Executor object. |