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