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