7d8df68b66b5e95d73ccca2092eb938ded9ad150
[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
35 from SCons.Debug import logInstanceCreation
36 import SCons.Memoize
37
38
39 class Executor:
40     """A class for controlling instances of executing an action.
41
42     This largely exists to hold a single association of an action,
43     environment, list of environment override dictionaries, targets
44     and sources for later processing as needed.
45     """
46
47     if SCons.Memoize.use_memoizer:
48         __metaclass__ = SCons.Memoize.Memoized_Metaclass
49
50     memoizer_counters = []
51
52     def __init__(self, action, env=None, overridelist=[{}],
53                  targets=[], sources=[], builder_kw={}):
54         if __debug__: logInstanceCreation(self, 'Executor.Executor')
55         self.set_action_list(action)
56         self.pre_actions = []
57         self.post_actions = []
58         self.env = env
59         self.overridelist = overridelist
60         self.targets = targets
61         self.sources = sources[:]
62         self.builder_kw = builder_kw
63         self._memo = {}
64
65     def set_action_list(self, action):
66         import SCons.Util
67         if not SCons.Util.is_List(action):
68             if not action:
69                 import SCons.Errors
70                 raise SCons.Errors.UserError, "Executor must have an action."
71             action = [action]
72         self.action_list = action
73
74     def get_action_list(self):
75         return self.pre_actions + self.action_list + self.post_actions
76
77     def get_build_env(self):
78         """Fetch or create the appropriate build Environment
79         for this Executor.
80         """
81         # Create the build environment instance with appropriate
82         # overrides.  These get evaluated against the current
83         # environment's construction variables so that users can
84         # add to existing values by referencing the variable in
85         # the expansion.
86         overrides = {}
87         for odict in self.overridelist:
88             overrides.update(odict)
89
90         import SCons.Defaults
91         env = self.env or SCons.Defaults.DefaultEnvironment()
92         build_env = env.Override(overrides)
93
94         return build_env
95
96     def get_build_scanner_path(self, scanner):
97         """Fetch the scanner path for this executor's targets
98         and sources.
99         """
100         env = self.get_build_env()
101         try:
102             cwd = self.targets[0].cwd
103         except (IndexError, AttributeError):
104             cwd = None
105         return scanner.path(env, cwd, self.targets, self.sources)
106
107     def get_kw(self, kw={}):
108         result = self.builder_kw.copy()
109         result.update(kw)
110         return result
111
112     def do_nothing(self, target, exitstatfunc, kw):
113         pass
114
115     def do_execute(self, target, exitstatfunc, kw):
116         """Actually execute the action list."""
117         env = self.get_build_env()
118         kw = self.get_kw(kw)
119         for act in self.get_action_list():
120             apply(act,
121                   (self.targets, self.sources, env, exitstatfunc),
122                   kw)
123
124     # use extra indirection because with new-style objects (Python 2.2
125     # and above) we can't override special methods, and nullify() needs
126     # to be able to do this.
127
128     def __call__(self, target, exitstatfunc, **kw):
129         self.do_execute(target, exitstatfunc, kw)
130
131     def cleanup(self):
132         self._memo = {}
133
134     def add_sources(self, sources):
135         """Add source files to this Executor's list.  This is necessary
136         for "multi" Builders that can be called repeatedly to build up
137         a source file list for a given target."""
138         slist = filter(lambda x, s=self.sources: x not in s, sources)
139         self.sources.extend(slist)
140
141     def add_pre_action(self, action):
142         self.pre_actions.append(action)
143
144     def add_post_action(self, action):
145         self.post_actions.append(action)
146
147     # another extra indirection for new-style objects and nullify...
148
149     def my_str(self):
150         env = self.get_build_env()
151         get = lambda action, t=self.targets, s=self.sources, e=env: \
152                      action.genstring(t, s, e)
153         return string.join(map(get, self.get_action_list()), "\n")
154
155
156     def __str__(self):
157         return self.my_str()
158
159     def nullify(self):
160         self.cleanup()
161         self.do_execute = self.do_nothing
162         self.my_str     = lambda S=self: ''
163
164     memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
165
166     def get_contents(self):
167         """Fetch the signature contents.  This is the main reason this
168         class exists, so we can compute this once and cache it regardless
169         of how many target or source Nodes there are.
170         """
171         try:
172             return self._memo['get_contents']
173         except KeyError:
174             pass
175         env = self.get_build_env()
176         get = lambda action, t=self.targets, s=self.sources, e=env: \
177                      action.get_contents(t, s, e)
178         result = string.join(map(get, self.get_action_list()), "")
179         self._memo['get_contents'] = result
180         return result
181
182     def get_timestamp(self):
183         """Fetch a time stamp for this Executor.  We don't have one, of
184         course (only files do), but this is the interface used by the
185         timestamp module.
186         """
187         return 0
188
189     def scan_targets(self, scanner):
190         self.scan(scanner, self.targets)
191
192     def scan_sources(self, scanner):
193         if self.sources:
194             self.scan(scanner, self.sources)
195
196     def scan(self, scanner, node_list):
197         """Scan a list of this Executor's files (targets or sources) for
198         implicit dependencies and update all of the targets with them.
199         This essentially short-circuits an N*M scan of the sources for
200         each individual target, which is a hell of a lot more efficient.
201         """
202         map(lambda N: N.disambiguate(), node_list)
203
204         env = self.get_build_env()
205         select_specific_scanner = lambda t: (t[0], t[1].select(t[0]))
206         remove_null_scanners = lambda t: not t[1] is None
207         add_scanner_path = lambda t, s=self: \
208                                   (t[0], t[1], s.get_build_scanner_path(t[1]))
209         if scanner:
210             scanner_list = map(lambda n, s=scanner: (n, s), node_list)
211         else:
212             kw = self.get_kw()
213             get_initial_scanners = lambda n, e=env, kw=kw: \
214                                           (n, n.get_env_scanner(e, kw))
215             scanner_list = map(get_initial_scanners, node_list)
216             scanner_list = filter(remove_null_scanners, scanner_list)
217
218         scanner_list = map(select_specific_scanner, scanner_list)
219         scanner_list = filter(remove_null_scanners, scanner_list)
220         scanner_path_list = map(add_scanner_path, scanner_list)
221         deps = []
222         for node, scanner, path in scanner_path_list:
223             deps.extend(node.get_implicit_deps(env, scanner, path))
224
225         for tgt in self.targets:
226             tgt.add_to_implicit(deps)
227
228     def get_missing_sources(self):
229         """
230         """
231         return filter(lambda s: s.missing(), self.sources)
232
233     def _get_unignored_sources_key(self, ignore=()):
234         return tuple(ignore)
235
236     memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
237
238     def get_unignored_sources(self, ignore=()):
239         ignore = tuple(ignore)
240         try:
241             memo_dict = self._memo['get_unignored_sources']
242         except KeyError:
243             memo_dict = {}
244             self._memo['get_unignored_sources'] = memo_dict
245         else:
246             try:
247                 return memo_dict[ignore]
248             except KeyError:
249                 pass
250
251         sourcelist = self.sources
252         if ignore:
253             sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist)
254
255         memo_dict[ignore] = sourcelist
256
257         return sourcelist
258
259     def _process_sources_key(self, func, ignore=()):
260         return (func, tuple(ignore))
261
262     memoizer_counters.append(SCons.Memoize.CountDict('process_sources', _process_sources_key))
263
264     def process_sources(self, func, ignore=()):
265         memo_key = (func, tuple(ignore))
266         try:
267             memo_dict = self._memo['process_sources']
268         except KeyError:
269             memo_dict = {}
270             self._memo['process_sources'] = memo_dict
271         else:
272             try:
273                 return memo_dict[memo_key]
274             except KeyError:
275                 pass
276
277         result = map(func, self.get_unignored_sources(ignore))
278
279         memo_dict[memo_key] = result
280
281         return result
282
283
284 _Executor = Executor
285
286 class Null(_Executor):
287     """A null Executor, with a null build Environment, that does
288     nothing when the rest of the methods call it.
289
290     This might be able to disapper when we refactor things to
291     disassociate Builders from Nodes entirely, so we're not
292     going to worry about unit tests for this--at least for now.
293     """
294     def __init__(self, *args, **kw):
295         if __debug__: logInstanceCreation(self, 'Executor.Null')
296         kw['action'] = []
297         apply(_Executor.__init__, (self,), kw)
298     def get_build_env(self):
299         import SCons.Util
300         return SCons.Util.Null()
301     def get_build_scanner_path(self):
302         return None
303     def cleanup(self):
304         pass