Fix calculation of $UNCHANGED_SOURCES to include correctly sources
[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
31 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
32
33 import string
34 import UserList
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(UserList.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 UserList.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     UserList.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(map(rfile, b.sources))
165                 ut.extend(b.targets)
166             else:
167                 cs.extend(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(map(lambda n: rfile(n).get_subst_proxy(), 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(map(lambda n: n.get_subst_proxy(), 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 = apply(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 = filter(lambda x, s=self.batches[0].sources: x not in s, 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         get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \
410                      action.genstring(t, s, e)
411         return string.join(map(get, self.get_action_list()), "\n")
412
413
414     def __str__(self):
415         return self.my_str()
416
417     def nullify(self):
418         self.cleanup()
419         self.do_execute = self.do_nothing
420         self.my_str     = lambda S=self: ''
421
422     memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
423
424     def get_contents(self):
425         """Fetch the signature contents.  This is the main reason this
426         class exists, so we can compute this once and cache it regardless
427         of how many target or source Nodes there are.
428         """
429         try:
430             return self._memo['get_contents']
431         except KeyError:
432             pass
433         env = self.get_build_env()
434         get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \
435                      action.get_contents(t, s, e)
436         result = string.join(map(get, self.get_action_list()), "")
437         self._memo['get_contents'] = result
438         return result
439
440     def get_timestamp(self):
441         """Fetch a time stamp for this Executor.  We don't have one, of
442         course (only files do), but this is the interface used by the
443         timestamp module.
444         """
445         return 0
446
447     def scan_targets(self, scanner):
448         # TODO(batch):  scan by batches
449         self.scan(scanner, self.get_all_targets())
450
451     def scan_sources(self, scanner):
452         # TODO(batch):  scan by batches
453         if self.batches[0].sources:
454             self.scan(scanner, self.get_all_sources())
455
456     def scan(self, scanner, node_list):
457         """Scan a list of this Executor's files (targets or sources) for
458         implicit dependencies and update all of the targets with them.
459         This essentially short-circuits an N*M scan of the sources for
460         each individual target, which is a hell of a lot more efficient.
461         """
462         env = self.get_build_env()
463
464         # TODO(batch):  scan by batches)
465         deps = []
466         if scanner:
467             for node in node_list:
468                 node.disambiguate()
469                 s = scanner.select(node)
470                 if not s:
471                     continue
472                 path = self.get_build_scanner_path(s)
473                 deps.extend(node.get_implicit_deps(env, s, path))
474         else:
475             kw = self.get_kw()
476             for node in node_list:
477                 node.disambiguate()
478                 scanner = node.get_env_scanner(env, kw)
479                 if not scanner:
480                     continue
481                 scanner = scanner.select(node)
482                 if not scanner:
483                     continue
484                 path = self.get_build_scanner_path(scanner)
485                 deps.extend(node.get_implicit_deps(env, scanner, path))
486
487         deps.extend(self.get_implicit_deps())
488
489         for tgt in self.get_all_targets():
490             tgt.add_to_implicit(deps)
491
492     def _get_unignored_sources_key(self, node, ignore=()):
493         return (node,) + tuple(ignore)
494
495     memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
496
497     def get_unignored_sources(self, node, ignore=()):
498         key = (node,) + tuple(ignore)
499         try:
500             memo_dict = self._memo['get_unignored_sources']
501         except KeyError:
502             memo_dict = {}
503             self._memo['get_unignored_sources'] = memo_dict
504         else:
505             try:
506                 return memo_dict[key]
507             except KeyError:
508                 pass
509
510         if node:
511             # TODO:  better way to do this (it's a linear search,
512             # but it may not be critical path)?
513             sourcelist = []
514             for b in self.batches:
515                 if node in b.targets:
516                     sourcelist = b.sources
517                     break
518         else:
519             sourcelist = self.get_all_sources()
520         if ignore:
521             idict = {}
522             for i in ignore:
523                 idict[i] = 1
524             sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist)
525
526         memo_dict[key] = sourcelist
527
528         return sourcelist
529
530     def get_implicit_deps(self):
531         """Return the executor's implicit dependencies, i.e. the nodes of
532         the commands to be executed."""
533         result = []
534         build_env = self.get_build_env()
535         for act in self.get_action_list():
536             deps = act.get_implicit_deps(self.get_all_targets(),
537                                          self.get_all_sources(),
538                                          build_env)
539             result.extend(deps)
540         return result
541
542
543
544 _batch_executors = {}
545
546 def GetBatchExecutor(key):
547     return _batch_executors[key]
548
549 def AddBatchExecutor(key, executor):
550     assert not _batch_executors.has_key(key)
551     _batch_executors[key] = executor
552
553 nullenv = None
554
555
556 def get_NullEnvironment():
557     """Use singleton pattern for Null Environments."""
558     global nullenv
559
560     import SCons.Util
561     class NullEnvironment(SCons.Util.Null):
562         import SCons.CacheDir
563         _CacheDir_path = None
564         _CacheDir = SCons.CacheDir.CacheDir(None)
565         def get_CacheDir(self):
566             return self._CacheDir
567
568     if not nullenv:
569         nullenv = NullEnvironment()
570     return nullenv
571
572 class Null:
573     """A null Executor, with a null build Environment, that does
574     nothing when the rest of the methods call it.
575
576     This might be able to disapper when we refactor things to
577     disassociate Builders from Nodes entirely, so we're not
578     going to worry about unit tests for this--at least for now.
579     """
580     def __init__(self, *args, **kw):
581         if __debug__: logInstanceCreation(self, 'Executor.Null')
582         self.batches = [Batch(kw['targets'][:], [])]
583     def get_build_env(self):
584         return get_NullEnvironment()
585     def get_build_scanner_path(self):
586         return None
587     def cleanup(self):
588         pass
589     def prepare(self):
590         pass
591     def get_unignored_sources(self, *args, **kw):
592         return tuple(())
593     def get_action_targets(self):
594         return []
595     def get_action_list(self):
596         return []
597     def get_all_targets(self):
598         return self.batches[0].targets
599     def get_all_sources(self):
600         return self.batches[0].targets[0].sources
601     def get_all_children(self):
602         return self.get_all_sources()
603     def get_all_prerequisites(self):
604         return []
605     def get_action_side_effects(self):
606         return []
607     def __call__(self, *args, **kw):
608         return 0
609     def get_contents(self):
610         return ''
611     def _morph(self):
612         """Morph this Null executor to a real Executor object."""
613         batches = self.batches
614         self.__class__ = Executor
615         self.__init__([])            
616         self.batches = batches
617
618     # The following methods require morphing this Null Executor to a
619     # real Executor object.
620
621     def add_pre_action(self, action):
622         self._morph()
623         self.add_pre_action(action)
624     def add_post_action(self, action):
625         self._morph()
626         self.add_post_action(action)
627     def set_action_list(self, action):
628         self._morph()
629         self.set_action_list(action)
630
631