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
40 """A class for controlling instances of executing an action.
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.
47 if SCons.Memoize.use_memoizer:
48 __metaclass__ = SCons.Memoize.Memoized_Metaclass
50 memoizer_counters = []
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)
57 self.post_actions = []
59 self.overridelist = overridelist
60 self.targets = targets
61 self.sources = sources[:]
62 self.builder_kw = builder_kw
65 def set_action_list(self, action):
67 if not SCons.Util.is_List(action):
70 raise SCons.Errors.UserError, "Executor must have an action."
72 self.action_list = action
74 def get_action_list(self):
75 return self.pre_actions + self.action_list + self.post_actions
77 def get_build_env(self):
78 """Fetch or create the appropriate build Environment
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
87 for odict in self.overridelist:
88 overrides.update(odict)
91 env = self.env or SCons.Defaults.DefaultEnvironment()
92 build_env = env.Override(overrides)
96 def get_build_scanner_path(self, scanner):
97 """Fetch the scanner path for this executor's targets
100 env = self.get_build_env()
102 cwd = self.targets[0].cwd
103 except (IndexError, AttributeError):
105 return scanner.path(env, cwd, self.targets, self.sources)
107 def get_kw(self, kw={}):
108 result = self.builder_kw.copy()
112 def do_nothing(self, target, exitstatfunc, kw):
115 def do_execute(self, target, exitstatfunc, kw):
116 """Actually execute the action list."""
117 env = self.get_build_env()
119 for act in self.get_action_list():
121 (self.targets, self.sources, env, exitstatfunc),
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.
128 def __call__(self, target, exitstatfunc, **kw):
129 self.do_execute(target, exitstatfunc, kw)
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)
141 def add_pre_action(self, action):
142 self.pre_actions.append(action)
144 def add_post_action(self, action):
145 self.post_actions.append(action)
147 # another extra indirection for new-style objects and nullify...
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")
161 self.do_execute = self.do_nothing
162 self.my_str = lambda S=self: ''
164 memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
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.
172 return self._memo['get_contents']
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
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
189 def scan_targets(self, scanner):
190 self.scan(scanner, self.targets)
192 def scan_sources(self, scanner):
194 self.scan(scanner, self.sources)
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.
202 map(lambda N: N.disambiguate(), node_list)
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]))
210 scanner_list = map(lambda n, s=scanner: (n, s), node_list)
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)
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)
222 for node, scanner, path in scanner_path_list:
223 deps.extend(node.get_implicit_deps(env, scanner, path))
225 for tgt in self.targets:
226 tgt.add_to_implicit(deps)
228 def get_missing_sources(self):
231 return filter(lambda s: s.missing(), self.sources)
233 def _get_unignored_sources_key(self, ignore=()):
236 memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
238 def get_unignored_sources(self, ignore=()):
239 ignore = tuple(ignore)
241 memo_dict = self._memo['get_unignored_sources']
244 self._memo['get_unignored_sources'] = memo_dict
247 return memo_dict[ignore]
251 sourcelist = self.sources
253 sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist)
255 memo_dict[ignore] = sourcelist
259 def _process_sources_key(self, func, ignore=()):
260 return (func, tuple(ignore))
262 memoizer_counters.append(SCons.Memoize.CountDict('process_sources', _process_sources_key))
264 def process_sources(self, func, ignore=()):
265 memo_key = (func, tuple(ignore))
267 memo_dict = self._memo['process_sources']
270 self._memo['process_sources'] = memo_dict
273 return memo_dict[memo_key]
277 result = map(func, self.get_unignored_sources(ignore))
279 memo_dict[memo_key] = result
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.
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.
294 def __init__(self, *args, **kw):
295 if __debug__: logInstanceCreation(self, 'Executor.Null')
297 apply(_Executor.__init__, (self,), kw)
298 def get_build_env(self):
300 return SCons.Util.Null()
301 def get_build_scanner_path(self):