3 A module for executing actions with specific lists of target and source
9 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
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.
31 __revision__ = "src/engine/SCons/Executor.py 3842 2008/12/20 22:59:52 scons"
35 from SCons.Debug import logInstanceCreation
41 """A class for controlling instances of executing an action.
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.
48 if SCons.Memoize.use_memoizer:
49 __metaclass__ = SCons.Memoize.Memoized_Metaclass
51 memoizer_counters = []
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)
58 self.post_actions = []
60 self.overridelist = overridelist
61 self.targets = targets
62 self.sources = sources[:]
63 self.sources_need_sorting = False
64 self.builder_kw = builder_kw
67 def set_action_list(self, action):
69 if not SCons.Util.is_List(action):
72 raise SCons.Errors.UserError, "Executor must have an action."
74 self.action_list = action
76 def get_action_list(self):
77 return self.pre_actions + self.action_list + self.post_actions
79 memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
81 def get_build_env(self):
82 """Fetch or create the appropriate build Environment
86 return self._memo['get_build_env']
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
96 for odict in self.overridelist:
97 overrides.update(odict)
100 env = self.env or SCons.Defaults.DefaultEnvironment()
101 build_env = env.Override(overrides)
103 self._memo['get_build_env'] = build_env
107 def get_build_scanner_path(self, scanner):
108 """Fetch the scanner path for this executor's targets and sources.
110 env = self.get_build_env()
112 cwd = self.targets[0].cwd
113 except (IndexError, AttributeError):
115 return scanner.path(env, cwd, self.targets, self.get_sources())
117 def get_kw(self, kw={}):
118 result = self.builder_kw.copy()
122 def do_nothing(self, target, kw):
125 def do_execute(self, target, kw):
126 """Actually execute the action list."""
127 env = self.get_build_env()
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
136 msg = "Error %s" % status
137 raise SCons.Errors.BuildError(
144 # use extra indirection because with new-style objects (Python 2.2
145 # and above) we can't override special methods, and nullify() needs
146 # to be able to do this.
148 def __call__(self, target, **kw):
149 return self.do_execute(target, kw)
154 def add_sources(self, sources):
155 """Add source files to this Executor's list. This is necessary
156 for "multi" Builders that can be called repeatedly to build up
157 a source file list for a given target."""
158 self.sources.extend(sources)
159 self.sources_need_sorting = True
161 def get_sources(self):
162 if self.sources_need_sorting:
163 self.sources = SCons.Util.uniquer_hashables(self.sources)
164 self.sources_need_sorting = False
169 Preparatory checks for whether this Executor can go ahead
170 and (try to) build its targets.
172 for s in self.get_sources():
174 msg = "Source `%s' not found, needed by target `%s'."
175 raise SCons.Errors.StopError, msg % (s, self.targets[0])
177 def add_pre_action(self, action):
178 self.pre_actions.append(action)
180 def add_post_action(self, action):
181 self.post_actions.append(action)
183 # another extra indirection for new-style objects and nullify...
186 env = self.get_build_env()
187 get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
188 action.genstring(t, s, e)
189 return string.join(map(get, self.get_action_list()), "\n")
197 self.do_execute = self.do_nothing
198 self.my_str = lambda S=self: ''
200 memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
202 def get_contents(self):
203 """Fetch the signature contents. This is the main reason this
204 class exists, so we can compute this once and cache it regardless
205 of how many target or source Nodes there are.
208 return self._memo['get_contents']
211 env = self.get_build_env()
212 get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
213 action.get_contents(t, s, e)
214 result = string.join(map(get, self.get_action_list()), "")
215 self._memo['get_contents'] = result
218 def get_timestamp(self):
219 """Fetch a time stamp for this Executor. We don't have one, of
220 course (only files do), but this is the interface used by the
225 def scan_targets(self, scanner):
226 self.scan(scanner, self.targets)
228 def scan_sources(self, scanner):
230 self.scan(scanner, self.get_sources())
232 def scan(self, scanner, node_list):
233 """Scan a list of this Executor's files (targets or sources) for
234 implicit dependencies and update all of the targets with them.
235 This essentially short-circuits an N*M scan of the sources for
236 each individual target, which is a hell of a lot more efficient.
238 env = self.get_build_env()
242 for node in node_list:
244 s = scanner.select(node)
247 path = self.get_build_scanner_path(s)
248 deps.extend(node.get_implicit_deps(env, s, path))
251 for node in node_list:
253 scanner = node.get_env_scanner(env, kw)
256 scanner = scanner.select(node)
259 path = self.get_build_scanner_path(scanner)
260 deps.extend(node.get_implicit_deps(env, scanner, path))
262 deps.extend(self.get_implicit_deps())
264 for tgt in self.targets:
265 tgt.add_to_implicit(deps)
267 def _get_unignored_sources_key(self, ignore=()):
270 memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
272 def get_unignored_sources(self, ignore=()):
273 ignore = tuple(ignore)
275 memo_dict = self._memo['get_unignored_sources']
278 self._memo['get_unignored_sources'] = memo_dict
281 return memo_dict[ignore]
285 sourcelist = self.get_sources()
290 sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist)
292 memo_dict[ignore] = sourcelist
296 def _process_sources_key(self, func, ignore=()):
297 return (func, tuple(ignore))
299 memoizer_counters.append(SCons.Memoize.CountDict('process_sources', _process_sources_key))
301 def process_sources(self, func, ignore=()):
302 memo_key = (func, tuple(ignore))
304 memo_dict = self._memo['process_sources']
307 self._memo['process_sources'] = memo_dict
310 return memo_dict[memo_key]
314 result = map(func, self.get_unignored_sources(ignore))
316 memo_dict[memo_key] = result
320 def get_implicit_deps(self):
321 """Return the executor's implicit dependencies, i.e. the nodes of
322 the commands to be executed."""
324 build_env = self.get_build_env()
325 for act in self.get_action_list():
326 result.extend(act.get_implicit_deps(self.targets, self.get_sources(), build_env))
331 def get_NullEnvironment():
332 """Use singleton pattern for Null Environments."""
336 class NullEnvironment(SCons.Util.Null):
337 import SCons.CacheDir
338 _CacheDir_path = None
339 _CacheDir = SCons.CacheDir.CacheDir(None)
340 def get_CacheDir(self):
341 return self._CacheDir
344 nullenv = NullEnvironment()
348 """A null Executor, with a null build Environment, that does
349 nothing when the rest of the methods call it.
351 This might be able to disapper when we refactor things to
352 disassociate Builders from Nodes entirely, so we're not
353 going to worry about unit tests for this--at least for now.
355 def __init__(self, *args, **kw):
356 if __debug__: logInstanceCreation(self, 'Executor.Null')
357 self.targets = kw['targets']
358 def get_build_env(self):
359 return get_NullEnvironment()
360 def get_build_scanner_path(self):
366 def get_unignored_sources(self, *args, **kw):
368 def get_action_list(self):
370 def __call__(self, *args, **kw):
372 def get_contents(self):
376 """Morph this Null executor to a real Executor object."""
377 self.__class__ = Executor
378 self.__init__([], targets=self.targets)
380 # The following methods require morphing this Null Executor to a
381 # real Executor object.
383 def add_pre_action(self, action):
385 self.add_pre_action(action)
386 def add_post_action(self, action):
388 self.add_post_action(action)
389 def set_action_list(self, action):
391 self.set_action_list(action)