3 A module for executing actions with specific lists of target and source
11 # Permission is hereby granted, free of charge, to any person obtaining
12 # a copy of this software and associated documentation files (the
13 # "Software"), to deal in the Software without restriction, including
14 # without limitation the rights to use, copy, modify, merge, publish,
15 # distribute, sublicense, and/or sell copies of the Software, and to
16 # permit persons to whom the Software is furnished to do so, subject to
17 # the following conditions:
19 # The above copyright notice and this permission notice shall be included
20 # in all copies or substantial portions of the Software.
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
23 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
24 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS
32 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
36 from SCons.Debug import logInstanceCreation
42 """Remembers exact association between targets
43 and sources of executor."""
44 def __init__(self, targets=[], sources=[]):
45 self.targets = targets
46 self.sources = sources
50 class TSList(collections.UserList):
51 """A class that implements $TARGETS or $SOURCES expansions by wrapping
52 an executor Method. This class is used in the Executor.lvars()
53 to delay creation of NodeList objects until they're needed.
55 Note that we subclass collections.UserList purely so that the
56 is_Sequence() function will identify an object of this class as
57 a list during variable expansion. We're not really using any
58 collections.UserList methods in practice.
60 def __init__(self, func):
62 def __getattr__(self, attr):
64 return getattr(nl, attr)
65 def __getitem__(self, i):
68 def __getslice__(self, i, j):
70 i = max(i, 0); j = max(j, 0)
80 """A class that implements $TARGET or $SOURCE expansions by wrapping
83 def __init__(self, func):
85 def __getattr__(self, attr):
87 return getattr(n, attr)
101 A function to return the results of a Node's rfile() method,
102 if it exists, and the Node itself otherwise (if it's a Value
107 except AttributeError:
114 """A class for controlling instances of executing an action.
116 This largely exists to hold a single association of an action,
117 environment, list of environment override dictionaries, targets
118 and sources for later processing as needed.
121 if SCons.Memoize.use_memoizer:
122 __metaclass__ = SCons.Memoize.Memoized_Metaclass
124 memoizer_counters = []
126 def __init__(self, action, env=None, overridelist=[{}],
127 targets=[], sources=[], builder_kw={}):
128 if __debug__: logInstanceCreation(self, 'Executor.Executor')
129 self.set_action_list(action)
130 self.pre_actions = []
131 self.post_actions = []
133 self.overridelist = overridelist
134 if targets or sources:
135 self.batches = [Batch(targets[:], sources[:])]
138 self.builder_kw = builder_kw
144 except AttributeError:
146 'CHANGED_SOURCES' : TSList(self._get_changed_sources),
147 'CHANGED_TARGETS' : TSList(self._get_changed_targets),
148 'SOURCE' : TSObject(self._get_source),
149 'SOURCES' : TSList(self._get_sources),
150 'TARGET' : TSObject(self._get_target),
151 'TARGETS' : TSList(self._get_targets),
152 'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources),
153 'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets),
157 def _get_changes(self):
162 for b in self.batches:
163 if b.targets[0].is_up_to_date():
164 us.extend(list(map(rfile, b.sources)))
167 cs.extend(list(map(rfile, b.sources)))
169 self._changed_sources_list = SCons.Util.NodeList(cs)
170 self._changed_targets_list = SCons.Util.NodeList(ct)
171 self._unchanged_sources_list = SCons.Util.NodeList(us)
172 self._unchanged_targets_list = SCons.Util.NodeList(ut)
174 def _get_changed_sources(self, *args, **kw):
176 return self._changed_sources_list
177 except AttributeError:
179 return self._changed_sources_list
181 def _get_changed_targets(self, *args, **kw):
183 return self._changed_targets_list
184 except AttributeError:
186 return self._changed_targets_list
188 def _get_source(self, *args, **kw):
189 #return SCons.Util.NodeList([rfile(self.batches[0].sources[0]).get_subst_proxy()])
190 return rfile(self.batches[0].sources[0]).get_subst_proxy()
192 def _get_sources(self, *args, **kw):
193 return SCons.Util.NodeList([rfile(n).get_subst_proxy() for n in self.get_all_sources()])
195 def _get_target(self, *args, **kw):
196 #return SCons.Util.NodeList([self.batches[0].targets[0].get_subst_proxy()])
197 return self.batches[0].targets[0].get_subst_proxy()
199 def _get_targets(self, *args, **kw):
200 return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
202 def _get_unchanged_sources(self, *args, **kw):
204 return self._unchanged_sources_list
205 except AttributeError:
207 return self._unchanged_sources_list
209 def _get_unchanged_targets(self, *args, **kw):
211 return self._unchanged_targets_list
212 except AttributeError:
214 return self._unchanged_targets_list
216 def get_action_targets(self):
217 if not self.action_list:
219 targets_string = self.action_list[0].get_targets(self.env, self)
220 if targets_string[0] == '$':
221 targets_string = targets_string[1:]
222 return self.get_lvars()[targets_string]
224 def set_action_list(self, action):
226 if not SCons.Util.is_List(action):
229 raise SCons.Errors.UserError, "Executor must have an action."
231 self.action_list = action
233 def get_action_list(self):
234 return self.pre_actions + self.action_list + self.post_actions
236 def get_all_targets(self):
237 """Returns all targets for all batches of this Executor."""
239 for batch in self.batches:
240 # TODO(1.5): remove the list() cast
241 result.extend(list(batch.targets))
244 def get_all_sources(self):
245 """Returns all sources for all batches of this Executor."""
247 for batch in self.batches:
248 # TODO(1.5): remove the list() cast
249 result.extend(list(batch.sources))
252 def get_all_children(self):
253 """Returns all unique children (dependencies) for all batches
256 The Taskmaster can recognize when it's already evaluated a
257 Node, so we don't have to make this list unique for its intended
258 canonical use case, but we expect there to be a lot of redundancy
259 (long lists of batched .cc files #including the same .h files
260 over and over), so removing the duplicates once up front should
261 save the Taskmaster a lot of work.
263 result = SCons.Util.UniqueList([])
264 for target in self.get_all_targets():
265 result.extend(target.children())
268 def get_all_prerequisites(self):
269 """Returns all unique (order-only) prerequisites for all batches
272 result = SCons.Util.UniqueList([])
273 for target in self.get_all_targets():
274 # TODO(1.5): remove the list() cast
275 result.extend(list(target.prerequisites))
278 def get_action_side_effects(self):
280 """Returns all side effects for all batches of this
281 Executor used by the underlying Action.
283 result = SCons.Util.UniqueList([])
284 for target in self.get_action_targets():
285 result.extend(target.side_effects)
288 memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
290 def get_build_env(self):
291 """Fetch or create the appropriate build Environment
295 return self._memo['get_build_env']
299 # Create the build environment instance with appropriate
300 # overrides. These get evaluated against the current
301 # environment's construction variables so that users can
302 # add to existing values by referencing the variable in
305 for odict in self.overridelist:
306 overrides.update(odict)
308 import SCons.Defaults
309 env = self.env or SCons.Defaults.DefaultEnvironment()
310 build_env = env.Override(overrides)
312 self._memo['get_build_env'] = build_env
316 def get_build_scanner_path(self, scanner):
317 """Fetch the scanner path for this executor's targets and sources.
319 env = self.get_build_env()
321 cwd = self.batches[0].targets[0].cwd
322 except (IndexError, AttributeError):
324 return scanner.path(env, cwd,
325 self.get_all_targets(),
326 self.get_all_sources())
328 def get_kw(self, kw={}):
329 result = self.builder_kw.copy()
331 result['executor'] = self
334 def do_nothing(self, target, kw):
337 def do_execute(self, target, kw):
338 """Actually execute the action list."""
339 env = self.get_build_env()
342 for act in self.get_action_list():
343 #args = (self.get_all_targets(), self.get_all_sources(), env)
345 status = act(*args, **kw)
346 if isinstance(status, SCons.Errors.BuildError):
347 status.executor = self
350 msg = "Error %s" % status
351 raise SCons.Errors.BuildError(
353 node=self.batches[0].targets,
358 # use extra indirection because with new-style objects (Python 2.2
359 # and above) we can't override special methods, and nullify() needs
360 # to be able to do this.
362 def __call__(self, target, **kw):
363 return self.do_execute(target, kw)
368 def add_sources(self, sources):
369 """Add source files to this Executor's list. This is necessary
370 for "multi" Builders that can be called repeatedly to build up
371 a source file list for a given target."""
372 # TODO(batch): extend to multiple batches
373 assert (len(self.batches) == 1)
374 # TODO(batch): remove duplicates?
375 sources = [x for x in sources if x not in self.batches[0].sources]
376 self.batches[0].sources.extend(sources)
378 def get_sources(self):
379 return self.batches[0].sources
381 def add_batch(self, targets, sources):
382 """Add pair of associated target and source to this Executor's list.
383 This is necessary for "batch" Builders that can be called repeatedly
384 to build up a list of matching target and source files that will be
385 used in order to update multiple target files at once from multiple
386 corresponding source files, for tools like MSVC that support it."""
387 self.batches.append(Batch(targets, sources))
391 Preparatory checks for whether this Executor can go ahead
392 and (try to) build its targets.
394 for s in self.get_all_sources():
396 msg = "Source `%s' not found, needed by target `%s'."
397 raise SCons.Errors.StopError, msg % (s, self.batches[0].targets[0])
399 def add_pre_action(self, action):
400 self.pre_actions.append(action)
402 def add_post_action(self, action):
403 self.post_actions.append(action)
405 # another extra indirection for new-style objects and nullify...
408 env = self.get_build_env()
409 return "\n".join([action.genstring(self.get_all_targets(),
410 self.get_all_sources(),
412 for action in self.get_action_list()])
420 self.do_execute = self.do_nothing
421 self.my_str = lambda: ''
423 memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
425 def get_contents(self):
426 """Fetch the signature contents. This is the main reason this
427 class exists, so we can compute this once and cache it regardless
428 of how many target or source Nodes there are.
431 return self._memo['get_contents']
434 env = self.get_build_env()
435 result = "".join([action.get_contents(self.get_all_targets(),
436 self.get_all_sources(),
438 for action in self.get_action_list()])
439 self._memo['get_contents'] = result
442 def get_timestamp(self):
443 """Fetch a time stamp for this Executor. We don't have one, of
444 course (only files do), but this is the interface used by the
449 def scan_targets(self, scanner):
450 # TODO(batch): scan by batches
451 self.scan(scanner, self.get_all_targets())
453 def scan_sources(self, scanner):
454 # TODO(batch): scan by batches
455 if self.batches[0].sources:
456 self.scan(scanner, self.get_all_sources())
458 def scan(self, scanner, node_list):
459 """Scan a list of this Executor's files (targets or sources) for
460 implicit dependencies and update all of the targets with them.
461 This essentially short-circuits an N*M scan of the sources for
462 each individual target, which is a hell of a lot more efficient.
464 env = self.get_build_env()
466 # TODO(batch): scan by batches)
469 for node in node_list:
471 s = scanner.select(node)
474 path = self.get_build_scanner_path(s)
475 deps.extend(node.get_implicit_deps(env, s, path))
478 for node in node_list:
480 scanner = node.get_env_scanner(env, kw)
483 scanner = scanner.select(node)
486 path = self.get_build_scanner_path(scanner)
487 deps.extend(node.get_implicit_deps(env, scanner, path))
489 deps.extend(self.get_implicit_deps())
491 for tgt in self.get_all_targets():
492 tgt.add_to_implicit(deps)
494 def _get_unignored_sources_key(self, node, ignore=()):
495 return (node,) + tuple(ignore)
497 memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
499 def get_unignored_sources(self, node, ignore=()):
500 key = (node,) + tuple(ignore)
502 memo_dict = self._memo['get_unignored_sources']
505 self._memo['get_unignored_sources'] = memo_dict
508 return memo_dict[key]
513 # TODO: better way to do this (it's a linear search,
514 # but it may not be critical path)?
516 for b in self.batches:
517 if node in b.targets:
518 sourcelist = b.sources
521 sourcelist = self.get_all_sources()
526 sourcelist = [s for s in sourcelist if s not in idict]
528 memo_dict[key] = sourcelist
532 def get_implicit_deps(self):
533 """Return the executor's implicit dependencies, i.e. the nodes of
534 the commands to be executed."""
536 build_env = self.get_build_env()
537 for act in self.get_action_list():
538 deps = act.get_implicit_deps(self.get_all_targets(),
539 self.get_all_sources(),
546 _batch_executors = {}
548 def GetBatchExecutor(key):
549 return _batch_executors[key]
551 def AddBatchExecutor(key, executor):
552 assert key not in _batch_executors
553 _batch_executors[key] = executor
558 def get_NullEnvironment():
559 """Use singleton pattern for Null Environments."""
563 class NullEnvironment(SCons.Util.Null):
564 import SCons.CacheDir
565 _CacheDir_path = None
566 _CacheDir = SCons.CacheDir.CacheDir(None)
567 def get_CacheDir(self):
568 return self._CacheDir
571 nullenv = NullEnvironment()
575 """A null Executor, with a null build Environment, that does
576 nothing when the rest of the methods call it.
578 This might be able to disapper when we refactor things to
579 disassociate Builders from Nodes entirely, so we're not
580 going to worry about unit tests for this--at least for now.
582 def __init__(self, *args, **kw):
583 if __debug__: logInstanceCreation(self, 'Executor.Null')
584 self.batches = [Batch(kw['targets'][:], [])]
585 def get_build_env(self):
586 return get_NullEnvironment()
587 def get_build_scanner_path(self):
593 def get_unignored_sources(self, *args, **kw):
595 def get_action_targets(self):
597 def get_action_list(self):
599 def get_all_targets(self):
600 return self.batches[0].targets
601 def get_all_sources(self):
602 return self.batches[0].targets[0].sources
603 def get_all_children(self):
604 return self.get_all_sources()
605 def get_all_prerequisites(self):
607 def get_action_side_effects(self):
609 def __call__(self, *args, **kw):
611 def get_contents(self):
614 """Morph this Null executor to a real Executor object."""
615 batches = self.batches
616 self.__class__ = Executor
618 self.batches = batches
620 # The following methods require morphing this Null Executor to a
621 # real Executor object.
623 def add_pre_action(self, action):
625 self.add_pre_action(action)
626 def add_post_action(self, action):
628 self.add_post_action(action)
629 def set_action_list(self, action):
631 self.set_action_list(action)
636 # indent-tabs-mode:nil
638 # vim: set expandtab tabstop=4 shiftwidth=4: