8f9b917d9c9420edd7dc5457b51a9feb520fce26
[scons.git] / src / engine / SCons / Executor.py
1 """SCons.Executor
2
3 A module for executing actions with specific lists of target and source
4 Nodes.
5
6 """
7
8 #
9 # __COPYRIGHT__
10 #
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:
18 #
19 # The above copyright notice and this permission notice shall be included
20 # in all copies or substantial portions of the Software.
21 #
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.
29 #
30 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
31
32 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
33
34 import collections
35
36 from SCons.Debug import logInstanceCreation
37 import SCons.Errors
38 import SCons.Memoize
39
40
41 class Batch:
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
47
48
49
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.
54
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.
59     """
60     def __init__(self, func):
61         self.func = func
62     def __getattr__(self, attr):
63         nl = self.func()
64         return getattr(nl, attr)
65     def __getitem__(self, i):
66         nl = self.func()
67         return nl[i]
68     def __getslice__(self, i, j):
69         nl = self.func()
70         i = max(i, 0); j = max(j, 0)
71         return nl[i:j]
72     def __str__(self):
73         nl = self.func()
74         return str(nl)
75     def __repr__(self):
76         nl = self.func()
77         return repr(nl)
78
79 class TSObject:
80     """A class that implements $TARGET or $SOURCE expansions by wrapping
81     an Executor method.
82     """
83     def __init__(self, func):
84         self.func = func
85     def __getattr__(self, attr):
86         n = self.func()
87         return getattr(n, attr)
88     def __str__(self):
89         n = self.func()
90         if n:
91             return str(n)
92         return ''
93     def __repr__(self):
94         n = self.func()
95         if n:
96             return repr(n)
97         return ''
98
99 def rfile(node):
100     """
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
103     Node, e.g.).
104     """
105     try:
106         rfile = node.rfile
107     except AttributeError:
108         return node
109     else:
110         return rfile()
111
112
113 class Executor:
114     """A class for controlling instances of executing an action.
115
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.
119     """
120
121     if SCons.Memoize.use_memoizer:
122         __metaclass__ = SCons.Memoize.Memoized_Metaclass
123
124     memoizer_counters = []
125
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 = []
132         self.env = env
133         self.overridelist = overridelist
134         if targets or sources:
135             self.batches = [Batch(targets[:], sources[:])]
136         else:
137             self.batches = []
138         self.builder_kw = builder_kw
139         self._memo = {}
140
141     def get_lvars(self):
142         try:
143             return self.lvars
144         except AttributeError:
145             self.lvars = {
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),
154             }
155             return self.lvars
156
157     def _get_changes(self):
158         cs = []
159         ct = []
160         us = []
161         ut = []
162         for b in self.batches:
163             if b.targets[0].is_up_to_date():
164                 us.extend(list(map(rfile, b.sources)))
165                 ut.extend(b.targets)
166             else:
167                 cs.extend(list(map(rfile, b.sources)))
168                 ct.extend(b.targets)
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)
173
174     def _get_changed_sources(self, *args, **kw):
175         try:
176             return self._changed_sources_list
177         except AttributeError:
178             self._get_changes()
179             return self._changed_sources_list
180
181     def _get_changed_targets(self, *args, **kw):
182         try:
183             return self._changed_targets_list
184         except AttributeError:
185             self._get_changes()
186             return self._changed_targets_list
187
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()
191
192     def _get_sources(self, *args, **kw):
193         return SCons.Util.NodeList([rfile(n).get_subst_proxy() for n in self.get_all_sources()])
194
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()
198
199     def _get_targets(self, *args, **kw):
200         return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
201
202     def _get_unchanged_sources(self, *args, **kw):
203         try:
204             return self._unchanged_sources_list
205         except AttributeError:
206             self._get_changes()
207             return self._unchanged_sources_list
208
209     def _get_unchanged_targets(self, *args, **kw):
210         try:
211             return self._unchanged_targets_list
212         except AttributeError:
213             self._get_changes()
214             return self._unchanged_targets_list
215
216     def get_action_targets(self):
217         if not self.action_list:
218             return []
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]
223
224     def set_action_list(self, action):
225         import SCons.Util
226         if not SCons.Util.is_List(action):
227             if not action:
228                 import SCons.Errors
229                 raise SCons.Errors.UserError, "Executor must have an action."
230             action = [action]
231         self.action_list = action
232
233     def get_action_list(self):
234         return self.pre_actions + self.action_list + self.post_actions
235
236     def get_all_targets(self):
237         """Returns all targets for all batches of this Executor."""
238         result = []
239         for batch in self.batches:
240             # TODO(1.5):  remove the list() cast
241             result.extend(list(batch.targets))
242         return result
243
244     def get_all_sources(self):
245         """Returns all sources for all batches of this Executor."""
246         result = []
247         for batch in self.batches:
248             # TODO(1.5):  remove the list() cast
249             result.extend(list(batch.sources))
250         return result
251
252     def get_all_children(self):
253         """Returns all unique children (dependencies) for all batches
254         of this Executor.
255
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.
262         """
263         result = SCons.Util.UniqueList([])
264         for target in self.get_all_targets():
265             result.extend(target.children())
266         return result
267
268     def get_all_prerequisites(self):
269         """Returns all unique (order-only) prerequisites for all batches
270         of this Executor.
271         """
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))
276         return result
277
278     def get_action_side_effects(self):
279
280         """Returns all side effects for all batches of this
281         Executor used by the underlying Action.
282         """
283         result = SCons.Util.UniqueList([])
284         for target in self.get_action_targets():
285             result.extend(target.side_effects)
286         return result
287
288     memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
289
290     def get_build_env(self):
291         """Fetch or create the appropriate build Environment
292         for this Executor.
293         """
294         try:
295             return self._memo['get_build_env']
296         except KeyError:
297             pass
298
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
303         # the expansion.
304         overrides = {}
305         for odict in self.overridelist:
306             overrides.update(odict)
307
308         import SCons.Defaults
309         env = self.env or SCons.Defaults.DefaultEnvironment()
310         build_env = env.Override(overrides)
311
312         self._memo['get_build_env'] = build_env
313
314         return build_env
315
316     def get_build_scanner_path(self, scanner):
317         """Fetch the scanner path for this executor's targets and sources.
318         """
319         env = self.get_build_env()
320         try:
321             cwd = self.batches[0].targets[0].cwd
322         except (IndexError, AttributeError):
323             cwd = None
324         return scanner.path(env, cwd,
325                             self.get_all_targets(),
326                             self.get_all_sources())
327
328     def get_kw(self, kw={}):
329         result = self.builder_kw.copy()
330         result.update(kw)
331         result['executor'] = self
332         return result
333
334     def do_nothing(self, target, kw):
335         return 0
336
337     def do_execute(self, target, kw):
338         """Actually execute the action list."""
339         env = self.get_build_env()
340         kw = self.get_kw(kw)
341         status = 0
342         for act in self.get_action_list():
343             #args = (self.get_all_targets(), self.get_all_sources(), env)
344             args = ([], [], env)
345             status = act(*args, **kw)
346             if isinstance(status, SCons.Errors.BuildError):
347                 status.executor = self
348                 raise status
349             elif status:
350                 msg = "Error %s" % status
351                 raise SCons.Errors.BuildError(
352                     errstr=msg, 
353                     node=self.batches[0].targets,
354                     executor=self, 
355                     action=act)
356         return status
357
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.
361
362     def __call__(self, target, **kw):
363         return self.do_execute(target, kw)
364
365     def cleanup(self):
366         self._memo = {}
367
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)
377
378     def get_sources(self):
379         return self.batches[0].sources
380
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))
388
389     def prepare(self):
390         """
391         Preparatory checks for whether this Executor can go ahead
392         and (try to) build its targets.
393         """
394         for s in self.get_all_sources():
395             if s.missing():
396                 msg = "Source `%s' not found, needed by target `%s'."
397                 raise SCons.Errors.StopError, msg % (s, self.batches[0].targets[0])
398
399     def add_pre_action(self, action):
400         self.pre_actions.append(action)
401
402     def add_post_action(self, action):
403         self.post_actions.append(action)
404
405     # another extra indirection for new-style objects and nullify...
406
407     def my_str(self):
408         env = self.get_build_env()
409         return "\n".join([action.genstring(self.get_all_targets(),
410                                            self.get_all_sources(),
411                                            env)
412                           for action in self.get_action_list()])
413
414
415     def __str__(self):
416         return self.my_str()
417
418     def nullify(self):
419         self.cleanup()
420         self.do_execute = self.do_nothing
421         self.my_str     = lambda: ''
422
423     memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
424
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.
429         """
430         try:
431             return self._memo['get_contents']
432         except KeyError:
433             pass
434         env = self.get_build_env()
435         result = "".join([action.get_contents(self.get_all_targets(),
436                                               self.get_all_sources(),
437                                               env)
438                           for action in self.get_action_list()])
439         self._memo['get_contents'] = result
440         return result
441
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
445         timestamp module.
446         """
447         return 0
448
449     def scan_targets(self, scanner):
450         # TODO(batch):  scan by batches
451         self.scan(scanner, self.get_all_targets())
452
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())
457
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.
463         """
464         env = self.get_build_env()
465
466         # TODO(batch):  scan by batches)
467         deps = []
468         if scanner:
469             for node in node_list:
470                 node.disambiguate()
471                 s = scanner.select(node)
472                 if not s:
473                     continue
474                 path = self.get_build_scanner_path(s)
475                 deps.extend(node.get_implicit_deps(env, s, path))
476         else:
477             kw = self.get_kw()
478             for node in node_list:
479                 node.disambiguate()
480                 scanner = node.get_env_scanner(env, kw)
481                 if not scanner:
482                     continue
483                 scanner = scanner.select(node)
484                 if not scanner:
485                     continue
486                 path = self.get_build_scanner_path(scanner)
487                 deps.extend(node.get_implicit_deps(env, scanner, path))
488
489         deps.extend(self.get_implicit_deps())
490
491         for tgt in self.get_all_targets():
492             tgt.add_to_implicit(deps)
493
494     def _get_unignored_sources_key(self, node, ignore=()):
495         return (node,) + tuple(ignore)
496
497     memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
498
499     def get_unignored_sources(self, node, ignore=()):
500         key = (node,) + tuple(ignore)
501         try:
502             memo_dict = self._memo['get_unignored_sources']
503         except KeyError:
504             memo_dict = {}
505             self._memo['get_unignored_sources'] = memo_dict
506         else:
507             try:
508                 return memo_dict[key]
509             except KeyError:
510                 pass
511
512         if node:
513             # TODO:  better way to do this (it's a linear search,
514             # but it may not be critical path)?
515             sourcelist = []
516             for b in self.batches:
517                 if node in b.targets:
518                     sourcelist = b.sources
519                     break
520         else:
521             sourcelist = self.get_all_sources()
522         if ignore:
523             idict = {}
524             for i in ignore:
525                 idict[i] = 1
526             sourcelist = [s for s in sourcelist if s not in idict]
527
528         memo_dict[key] = sourcelist
529
530         return sourcelist
531
532     def get_implicit_deps(self):
533         """Return the executor's implicit dependencies, i.e. the nodes of
534         the commands to be executed."""
535         result = []
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(),
540                                          build_env)
541             result.extend(deps)
542         return result
543
544
545
546 _batch_executors = {}
547
548 def GetBatchExecutor(key):
549     return _batch_executors[key]
550
551 def AddBatchExecutor(key, executor):
552     assert key not in _batch_executors
553     _batch_executors[key] = executor
554
555 nullenv = None
556
557
558 def get_NullEnvironment():
559     """Use singleton pattern for Null Environments."""
560     global nullenv
561
562     import SCons.Util
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
569
570     if not nullenv:
571         nullenv = NullEnvironment()
572     return nullenv
573
574 class Null:
575     """A null Executor, with a null build Environment, that does
576     nothing when the rest of the methods call it.
577
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.
581     """
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):
588         return None
589     def cleanup(self):
590         pass
591     def prepare(self):
592         pass
593     def get_unignored_sources(self, *args, **kw):
594         return tuple(())
595     def get_action_targets(self):
596         return []
597     def get_action_list(self):
598         return []
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):
606         return []
607     def get_action_side_effects(self):
608         return []
609     def __call__(self, *args, **kw):
610         return 0
611     def get_contents(self):
612         return ''
613     def _morph(self):
614         """Morph this Null executor to a real Executor object."""
615         batches = self.batches
616         self.__class__ = Executor
617         self.__init__([])            
618         self.batches = batches
619
620     # The following methods require morphing this Null Executor to a
621     # real Executor object.
622
623     def add_pre_action(self, action):
624         self._morph()
625         self.add_pre_action(action)
626     def add_post_action(self, action):
627         self._morph()
628         self.add_post_action(action)
629     def set_action_list(self, action):
630         self._morph()
631         self.set_action_list(action)
632
633
634 # Local Variables:
635 # tab-width:4
636 # indent-tabs-mode:nil
637 # End:
638 # vim: set expandtab tabstop=4 shiftwidth=4: