3 A module for executing actions with specific lists of target and source
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__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
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(errstr=msg, executor=self, action=act)
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.
144 def __call__(self, target, **kw):
145 return self.do_execute(target, kw)
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
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
165 Preparatory checks for whether this Executor can go ahead
166 and (try to) build its targets.
168 for s in self.get_sources():
170 msg = "Source `%s' not found, needed by target `%s'."
171 raise SCons.Errors.StopError, msg % (s, self.targets[0])
173 def add_pre_action(self, action):
174 self.pre_actions.append(action)
176 def add_post_action(self, action):
177 self.post_actions.append(action)
179 # another extra indirection for new-style objects and nullify...
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")
193 self.do_execute = self.do_nothing
194 self.my_str = lambda S=self: ''
196 memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
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.
204 return self._memo['get_contents']
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
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
221 def scan_targets(self, scanner):
222 self.scan(scanner, self.targets)
224 def scan_sources(self, scanner):
226 self.scan(scanner, self.get_sources())
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.
234 env = self.get_build_env()
238 for node in node_list:
240 scanner = scanner.select(node)
243 path = self.get_build_scanner_path(scanner)
244 deps.extend(node.get_implicit_deps(env, scanner, path))
247 for node in node_list:
249 scanner = node.get_env_scanner(env, kw)
252 scanner = scanner.select(node)
255 path = self.get_build_scanner_path(scanner)
256 deps.extend(node.get_implicit_deps(env, scanner, path))
258 deps.extend(self.get_implicit_deps())
260 for tgt in self.targets:
261 tgt.add_to_implicit(deps)
263 def _get_unignored_sources_key(self, ignore=()):
266 memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
268 def get_unignored_sources(self, ignore=()):
269 ignore = tuple(ignore)
271 memo_dict = self._memo['get_unignored_sources']
274 self._memo['get_unignored_sources'] = memo_dict
277 return memo_dict[ignore]
281 sourcelist = self.get_sources()
286 sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist)
288 memo_dict[ignore] = sourcelist
292 def _process_sources_key(self, func, ignore=()):
293 return (func, tuple(ignore))
295 memoizer_counters.append(SCons.Memoize.CountDict('process_sources', _process_sources_key))
297 def process_sources(self, func, ignore=()):
298 memo_key = (func, tuple(ignore))
300 memo_dict = self._memo['process_sources']
303 self._memo['process_sources'] = memo_dict
306 return memo_dict[memo_key]
310 result = map(func, self.get_unignored_sources(ignore))
312 memo_dict[memo_key] = result
316 def get_implicit_deps(self):
317 """Return the executor's implicit dependencies, i.e. the nodes of
318 the commands to be executed."""
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))
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.
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.
336 def __init__(self, *args, **kw):
337 if __debug__: logInstanceCreation(self, 'Executor.Null')
339 apply(_Executor.__init__, (self,), kw)
340 def get_build_env(self):
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):