~ubuntu-branches/ubuntu/utopic/scons/utopic-proposed

« back to all changes in this revision

Viewing changes to engine/SCons/Taskmaster.py

  • Committer: Bazaar Package Importer
  • Author(s): Mark Brown
  • Date: 2004-08-24 08:57:22 UTC
  • mfrom: (0.2.1 upstream) (1.1.1 warty)
  • Revision ID: james.westby@ubuntu.com-20040824085722-hfk4f0pjbyu0ebxv
Tags: 0.96.1-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
"""
6
6
 
7
7
#
8
 
# Copyright (c) 2001, 2002 Steven Knight
 
8
# Copyright (c) 2001, 2002, 2003, 2004 The SCons Foundation
9
9
#
10
10
# Permission is hereby granted, free of charge, to any person obtaining
11
11
# a copy of this software and associated documentation files (the
27
27
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
28
#
29
29
 
30
 
__revision__ = "src/engine/SCons/Taskmaster.py 0.D006 2002/03/28 02:47:47 software"
31
 
 
32
 
 
33
 
 
 
30
__revision__ = "/home/scons/scons/branch.0/baseline/src/engine/SCons/Taskmaster.py 0.96.1.D001 2004/08/23 09:55:29 knight"
 
31
 
 
32
import string
 
33
import sys
 
34
import traceback
34
35
 
35
36
import SCons.Node
36
 
import string
37
37
import SCons.Errors
38
 
import copy
39
38
 
40
39
class Task:
41
40
    """Default SCons build engine task.
42
 
    
 
41
 
43
42
    This controls the interaction of the actual building of node
44
43
    and the rest of the engine.
45
44
 
50
49
    needs to customze something by sub-classing Taskmaster (or
51
50
    some other build engine class), we should first try to migrate
52
51
    that functionality into this class.
53
 
    
 
52
 
54
53
    Note that it's generally a good idea for sub-classes to call
55
54
    these methods explicitly to update state, etc., rather than
56
55
    roll their own interaction with Taskmaster from scratch."""
57
 
    def __init__(self, tm, targets, top, scanner = None):
 
56
    def __init__(self, tm, targets, top, node):
58
57
        self.tm = tm
59
58
        self.targets = targets
60
59
        self.top = top
61
 
        self.scanner = scanner
 
60
        self.node = node
 
61
 
 
62
    def display(self, message):
 
63
        """Allow the calling interface to display a message
 
64
        """
 
65
        pass
 
66
 
 
67
    def prepare(self):
 
68
        """Called just before the task is executed.
 
69
 
 
70
        This unlinks all targets and makes all directories before
 
71
        building anything."""
 
72
 
 
73
        # Now that it's the appropriate time, give the TaskMaster a
 
74
        # chance to raise any exceptions it encountered while preparing
 
75
        # this task.
 
76
        self.tm.exception_raise()
 
77
 
 
78
        if self.tm.message:
 
79
            self.display(self.tm.message)
 
80
            self.tm.message = None
 
81
 
 
82
        for t in self.targets:
 
83
            t.prepare()
 
84
            for s in t.side_effects:
 
85
                s.prepare()
62
86
 
63
87
    def execute(self):
64
 
        if self.targets[0].get_state() != SCons.Node.up_to_date:
65
 
            self.targets[0].prepare()
66
 
            self.targets[0].build()
 
88
        """Called to execute the task.
 
89
 
 
90
        This method is called from multiple threads in a parallel build,
 
91
        so only do thread safe stuff here.  Do thread unsafe stuff in
 
92
        prepare(), executed() or failed()."""
 
93
 
 
94
        try:
 
95
            everything_was_cached = 1
 
96
            for t in self.targets:
 
97
                if not t.retrieve_from_cache():
 
98
                    everything_was_cached = 0
 
99
                    break
 
100
            if not everything_was_cached:
 
101
                self.targets[0].build()
 
102
        except KeyboardInterrupt:
 
103
            raise
 
104
        except SystemExit:
 
105
            exc_value = sys.exc_info()[1]
 
106
            raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code)
 
107
        except SCons.Errors.UserError:
 
108
            raise
 
109
        except SCons.Errors.BuildError:
 
110
            raise
 
111
        except:
 
112
            exc_type, exc_value, exc_traceback = sys.exc_info()
 
113
            raise SCons.Errors.BuildError(self.targets[0],
 
114
                                          "Exception",
 
115
                                          exc_type,
 
116
                                          exc_value,
 
117
                                          exc_traceback)
67
118
 
68
119
    def get_target(self):
69
120
        """Fetch the target being built or updated by this task.
70
121
        """
71
 
        return self.targets[0]
72
 
 
73
 
    def set_tstates(self, state):
74
 
        """Set all of the target nodes's states."""
75
 
        for t in self.targets:
76
 
            t.set_state(state)
 
122
        return self.node
77
123
 
78
124
    def executed(self):
79
125
        """Called when the task has been successfully executed.
83
129
        things.  Most importantly, this calls back to the
84
130
        Taskmaster to put any node tasks waiting on this one
85
131
        back on the pending list."""
 
132
 
86
133
        if self.targets[0].get_state() == SCons.Node.executing:
87
 
            self.set_tstates(SCons.Node.executed)
88
 
            for t in self.targets:
89
 
                t.store_sigs()
90
 
            parents = {}
91
 
            for p in reduce(lambda x, y: x + y.get_parents(), self.targets, []):
92
 
                parents[p] = 1
93
 
            ready = filter(lambda x, s=self.scanner:
94
 
                                  (x.get_state() == SCons.Node.pending
95
 
                                   and x.children_are_executed(s)),
96
 
                           parents.keys())
97
 
            tasks = {}
98
 
            for t in map(lambda r: r.task, ready):
99
 
                tasks[t] = 1
100
 
            self.tm.pending_to_ready(tasks.keys())
 
134
            for t in self.targets:
 
135
                for side_effect in t.side_effects:
 
136
                    side_effect.set_state(None)
 
137
                t.set_state(SCons.Node.executed)
 
138
                t.built()
 
139
        else:
 
140
            for t in self.targets:
 
141
                t.visited()
 
142
 
 
143
        self.tm.executed(self.node)
101
144
 
102
145
    def failed(self):
103
146
        """Default action when a task fails:  stop the build."""
105
148
 
106
149
    def fail_stop(self):
107
150
        """Explicit stop-the-build failure."""
108
 
        self.set_tstates(SCons.Node.failed)
 
151
        for t in self.targets:
 
152
            t.set_state(SCons.Node.failed)
 
153
        self.tm.failed(self.node)
109
154
        self.tm.stop()
110
155
 
111
156
    def fail_continue(self):
114
159
        This sets failure status on the target nodes and all of
115
160
        their dependent parent nodes.
116
161
        """
117
 
        nodes = {}
118
 
        for t in self.targets:
119
 
            def get_parents(node, parent): return node.get_parents()
120
 
            def set_nodes(node, parent, nodes=nodes): nodes[node] = 1
121
 
            walker = SCons.Node.Walker(t, get_parents, eval_func=set_nodes)
122
 
            n = walker.next()
123
 
            while n:
124
 
                n = walker.next()
125
 
        pending = filter(lambda x: x.get_state() == SCons.Node.pending,
126
 
                         nodes.keys())
127
 
        tasks = {}
128
 
        for t in map(lambda r: r.task, pending):
129
 
            tasks[t] = 1
130
 
        self.tm.pending_remove(tasks.keys())
131
 
 
132
 
    def make_ready(self):
133
 
        """Make a task ready for execution."""
134
 
        state = SCons.Node.up_to_date
135
 
        for t in self.targets:
136
 
            bsig = self.tm.calc.bsig(t)
137
 
            t.set_bsig(bsig)
138
 
            if not self.tm.calc.current(t, bsig):
139
 
                state = SCons.Node.executing
140
 
        self.set_tstates(state)
141
 
        self.tm.add_ready(self)
142
 
 
143
 
 
144
 
 
145
 
class Calc:
146
 
    def bsig(self, node):
147
 
        """
148
 
        """
149
 
        return None
150
 
 
151
 
    def current(self, node, sig):
152
 
        """Default SCons build engine is-it-current function.
153
 
    
154
 
        This returns "always out of date," so every node is always
155
 
        built/visited.
156
 
        """
157
 
        return 0
158
 
 
 
162
        for t in self.targets:
 
163
            # Set failure state on all of the parents that were dependent
 
164
            # on this failed build.
 
165
            def set_state(node): node.set_state(SCons.Node.failed)
 
166
            t.call_for_all_waiting_parents(set_state)
 
167
 
 
168
        self.tm.executed(self.node)
 
169
 
 
170
    def mark_targets(self, state):
 
171
        for t in self.targets:
 
172
            t.set_state(state)
 
173
 
 
174
    def mark_targets_and_side_effects(self, state):
 
175
        for t in self.targets:
 
176
            for side_effect in t.side_effects:
 
177
                side_effect.set_state(state)
 
178
            t.set_state(state)
 
179
 
 
180
    def make_ready_all(self):
 
181
        """Mark all targets in a task ready for execution.
 
182
 
 
183
        This is used when the interface needs every target Node to be
 
184
        visited--the canonical example being the "scons -c" option.
 
185
        """
 
186
        self.out_of_date = self.targets[:]
 
187
        self.mark_targets_and_side_effects(SCons.Node.executing)
 
188
 
 
189
    def make_ready_current(self):
 
190
        """Mark all targets in a task ready for execution if any target
 
191
        is not current.
 
192
 
 
193
        This is the default behavior for building only what's necessary.
 
194
        """
 
195
        self.out_of_date = []
 
196
        for t in self.targets:
 
197
            if not t.current():
 
198
                self.out_of_date.append(t)
 
199
        if self.out_of_date:
 
200
            self.mark_targets_and_side_effects(SCons.Node.executing)
 
201
        else:
 
202
            self.mark_targets(SCons.Node.up_to_date)
 
203
 
 
204
    make_ready = make_ready_current
 
205
 
 
206
    def postprocess(self):
 
207
        """Post process a task after it's been executed."""
 
208
        for t in self.targets:
 
209
            t.postprocess()
 
210
 
 
211
    def exc_info(self):
 
212
        return self.tm.exception
 
213
 
 
214
    def exc_clear(self):
 
215
        self.tm.exception_clear()
 
216
 
 
217
    def exception_set(self):
 
218
        self.tm.exception_set()
 
219
 
 
220
 
 
221
 
 
222
def order(dependencies):
 
223
    """Re-order a list of dependencies (if we need to)."""
 
224
    return dependencies
159
225
 
160
226
 
161
227
class Taskmaster:
162
228
    """A generic Taskmaster for handling a bunch of targets.
163
229
 
164
230
    Classes that override methods of this class should call
165
 
    the base class method, so this class can do its thing.    
 
231
    the base class method, so this class can do its thing.
166
232
    """
167
233
 
168
 
    def __init__(self, targets=[], tasker=Task, calc=Calc()):
169
 
        
170
 
        def out_of_date(node, parent):
171
 
            if node.get_state():
172
 
                # The state is set, so someone has already been here
173
 
                # (finished or currently executing).  Find another one.
174
 
                return []
175
 
            # Scan the file before fetching its children().
176
 
            if parent:
177
 
                scanner = parent.src_scanner_get(node.scanner_key())
178
 
            else:
179
 
                scanner = None
180
 
            return filter(lambda x: x.get_state() != SCons.Node.up_to_date,
181
 
                          node.children(scanner))
182
 
 
183
 
        def cycle_error(node, stack):
184
 
            if node.builder:
185
 
                nodes = stack + [node]
 
234
    def __init__(self, targets=[], tasker=Task, order=order):
 
235
        self.targets = targets # top level targets
 
236
        self.candidates = targets[:] # nodes that might be ready to be executed
 
237
        self.candidates.reverse()
 
238
        self.executing = [] # nodes that are currently executing
 
239
        self.pending = [] # nodes that depend on a currently executing node
 
240
        self.tasker = tasker
 
241
        self.ready = None # the next task that is ready to be executed
 
242
        self.order = order
 
243
        self.exception_clear()
 
244
        self.message = None
 
245
 
 
246
    def _find_next_ready_node(self):
 
247
        """Find the next node that is ready to be built"""
 
248
 
 
249
        if self.ready:
 
250
            return
 
251
 
 
252
        while self.candidates:
 
253
            node = self.candidates[-1]
 
254
            state = node.get_state()
 
255
 
 
256
            # Skip this node if it has already been executed:
 
257
            if state != None and state != SCons.Node.stack:
 
258
                self.candidates.pop()
 
259
                continue
 
260
 
 
261
            # Mark this node as being on the execution stack:
 
262
            node.set_state(SCons.Node.stack)
 
263
 
 
264
            try:
 
265
                children = node.children()
 
266
            except SystemExit:
 
267
                exc_value = sys.exc_info()[1]
 
268
                e = SCons.Errors.ExplicitExit(node, exc_value.code)
 
269
                self.exception_set((SCons.Errors.ExplicitExit, e))
 
270
                self.candidates.pop()
 
271
                self.ready = node
 
272
                break
 
273
            except KeyboardInterrupt:
 
274
                raise
 
275
            except:
 
276
                # We had a problem just trying to figure out the
 
277
                # children (like a child couldn't be linked in to a
 
278
                # BuildDir, or a Scanner threw something).  Arrange to
 
279
                # raise the exception when the Task is "executed."
 
280
                self.exception_set()
 
281
                self.candidates.pop()
 
282
                self.ready = node
 
283
                break
 
284
 
 
285
            # Detect dependency cycles:
 
286
            def in_stack(node): return node.get_state() == SCons.Node.stack
 
287
            cycle = filter(in_stack, children)
 
288
            if cycle:
 
289
                nodes = filter(in_stack, self.candidates) + cycle
186
290
                nodes.reverse()
187
291
                desc = "Dependency cycle: " + string.join(map(str, nodes), " -> ")
188
292
                raise SCons.Errors.UserError, desc
189
293
 
190
 
        def eval_node(node, parent, self=self):
191
 
            if node.get_state():
192
 
                # The state is set, so someone has already been here
193
 
                # (finished or currently executing).  Find another one.
194
 
                return
195
 
            if not node.builder:
196
 
                # It's a source file, we don't need to build it,
197
 
                # but mark it as "up to date" so targets won't
198
 
                # wait for it.
199
 
                node.set_state(SCons.Node.up_to_date)
200
 
                # set the signature for non-derived files
201
 
                # here so they don't get recalculated over
202
 
                # and over again:
203
 
                node.set_csig(self.calc.csig(node))
204
 
                return
 
294
            # Find all of the derived dependencies (that is,
 
295
            # children who have builders or are side effects):
205
296
            try:
206
 
                tlist = node.builder.targets(node)
207
 
            except AttributeError:
208
 
                tlist = [ node ]
209
 
            if parent:
210
 
                scanner = parent.src_scanner_get(node.scanner_key())
211
 
            else:
212
 
                scanner = None
213
 
            task = self.tasker(self, tlist, self.walkers[0].is_done(), scanner)
214
 
            if not tlist[0].children_are_executed(scanner):
215
 
                for t in tlist:
216
 
                    t.set_state(SCons.Node.pending)
217
 
                    t.task = task
218
 
                self.pending = self.pending + 1
219
 
                return
220
 
            task.make_ready()
221
 
 
222
 
        #XXX In Python 2.2 we can get rid of f1, f2 and f3:
223
 
        self.walkers = map(lambda x, f1=out_of_date,
224
 
                                     f2=cycle_error,
225
 
                                     f3=eval_node:
226
 
                                  SCons.Node.Walker(x, f1, f2, f3),
227
 
                           targets)
228
 
        self.tasker = tasker
229
 
        self.calc = calc
230
 
        self.ready = []
231
 
        self.pending = 0
232
 
        
233
 
        self._find_next_ready_node()
 
297
                def derived_nodes(node): return node.is_derived() or node.is_pseudo_derived()
 
298
                derived = filter(derived_nodes, children)
 
299
            except KeyboardInterrupt:
 
300
                raise
 
301
            except:
 
302
                # We had a problem just trying to figure out if any of
 
303
                # the kids are derived (like a child couldn't be linked
 
304
                # from a repository).  Arrange to raise the exception
 
305
                # when the Task is "executed."
 
306
                self.exception_set()
 
307
                self.candidates.pop()
 
308
                self.ready = node
 
309
                break
 
310
 
 
311
            # If there aren't any children with builders and this
 
312
            # was a top-level argument, then see if we can find any
 
313
            # corresponding targets in linked build directories:
 
314
            if not derived and node in self.targets:
 
315
                alt, message = node.alter_targets()
 
316
                if alt:
 
317
                    self.message = message
 
318
                    self.candidates.pop()
 
319
                    self.candidates.extend(alt)
 
320
                    continue
 
321
 
 
322
            # Add derived files that have not been built
 
323
            # to the candidates list:
 
324
            def unbuilt_nodes(node): return node.get_state() == None
 
325
            not_built = filter(unbuilt_nodes, derived)
 
326
            if not_built:
 
327
                # We're waiting on one more derived files that have not
 
328
                # yet been built.  Add this node to the waiting_parents
 
329
                # list of each of those derived files.
 
330
                def add_to_waiting_parents(child, parent=node):
 
331
                    child.add_to_waiting_parents(parent)
 
332
                map(add_to_waiting_parents, not_built)
 
333
                not_built.reverse()
 
334
                self.candidates.extend(self.order(not_built))
 
335
                continue
 
336
 
 
337
            # Skip this node if it has side-effects that are
 
338
            # currently being built:
 
339
            cont = 0
 
340
            for side_effect in node.side_effects:
 
341
                if side_effect.get_state() == SCons.Node.executing:
 
342
                    self.pending.append(node)
 
343
                    node.set_state(SCons.Node.pending)
 
344
                    self.candidates.pop()
 
345
                    cont = 1
 
346
                    break
 
347
            if cont: continue
 
348
 
 
349
            # Skip this node if it is pending on a currently
 
350
            # executing node:
 
351
            if node.depends_on(self.executing) or node.depends_on(self.pending):
 
352
                self.pending.append(node)
 
353
                node.set_state(SCons.Node.pending)
 
354
                self.candidates.pop()
 
355
                continue
 
356
 
 
357
            # The default when we've gotten through all of the checks above:
 
358
            # this node is ready to be built.
 
359
            self.candidates.pop()
 
360
            self.ready = node
 
361
            break
234
362
 
235
363
    def next_task(self):
236
364
        """Return the next task to be executed."""
237
 
        if self.ready:
238
 
            task = self.ready.pop()
239
 
            if not self.ready:
240
 
                self._find_next_ready_node()
241
 
            return task
242
 
        else:
 
365
 
 
366
        self._find_next_ready_node()
 
367
 
 
368
        node = self.ready
 
369
 
 
370
        if node is None:
243
371
            return None
244
372
 
245
 
    def _find_next_ready_node(self):
246
 
        """Find the next node that is ready to be built"""
247
 
        while self.walkers:
248
 
            n = self.walkers[0].next()
249
 
            if n == None:
250
 
                self.walkers.pop(0)
251
 
                continue
252
 
            if self.ready:
253
 
                return
254
 
            
 
373
        try:
 
374
            tlist = node.builder.targets(node)
 
375
        except AttributeError:
 
376
            tlist = [node]
 
377
        self.executing.extend(tlist)
 
378
        self.executing.extend(node.side_effects)
 
379
        
 
380
        task = self.tasker(self, tlist, node in self.targets, node)
 
381
        try:
 
382
            task.make_ready()
 
383
        except KeyboardInterrupt:
 
384
            raise
 
385
        except:
 
386
            # We had a problem just trying to get this task ready (like
 
387
            # a child couldn't be linked in to a BuildDir when deciding
 
388
            # whether this node is current).  Arrange to raise the
 
389
            # exception when the Task is "executed."
 
390
            self.exception_set()
 
391
        self.ready = None
 
392
 
 
393
        return task
 
394
 
255
395
    def is_blocked(self):
256
 
        return not self.ready and self.pending
 
396
        self._find_next_ready_node()
 
397
 
 
398
        return not self.ready and (self.pending or self.executing)
257
399
 
258
400
    def stop(self):
259
401
        """Stop the current build completely."""
260
 
        self.walkers = []
261
 
        self.pending = 0
262
 
        self.ready = []
263
 
 
264
 
    def add_ready(self, task):
265
 
        """Add a task to the ready queue.
266
 
        """
267
 
        self.ready.append(task)
268
 
 
269
 
    def pending_to_ready(self, tasks):
270
 
        """Move the specified tasks from the pending count
271
 
        to the 'ready' queue.
272
 
        """
273
 
        self.pending_remove(tasks)
274
 
        for t in tasks:
275
 
            t.make_ready()
276
 
 
277
 
    def pending_remove(self, tasks):
278
 
        """Remove tasks from the pending count.
279
 
        
280
 
        We assume that the caller has already confirmed that
281
 
        the nodes in this task are in pending state.
282
 
        """
283
 
        self.pending = self.pending - len(tasks)
 
402
        self.candidates = []
 
403
        self.ready = None
 
404
        self.pending = []
 
405
 
 
406
    def failed(self, node):
 
407
        try:
 
408
            tlist = node.builder.targets(node)
 
409
        except AttributeError:
 
410
            tlist = [node]
 
411
        for t in tlist:
 
412
            self.executing.remove(t)
 
413
        for side_effect in node.side_effects:
 
414
            self.executing.remove(side_effect)
 
415
 
 
416
    def executed(self, node):
 
417
        try:
 
418
            tlist = node.builder.targets(node)
 
419
        except AttributeError:
 
420
            tlist = [node]
 
421
        for t in tlist:
 
422
            self.executing.remove(t)
 
423
        for side_effect in node.side_effects:
 
424
            self.executing.remove(side_effect)
 
425
 
 
426
        # move the current pending nodes to the candidates list:
 
427
        # (they may not all be ready to build, but _find_next_ready_node()
 
428
        #  will figure out which ones are really ready)
 
429
        for node in self.pending:
 
430
            node.set_state(None)
 
431
        self.pending.reverse()
 
432
        self.candidates.extend(self.pending)
 
433
        self.pending = []
 
434
 
 
435
    def exception_set(self, exception=None):
 
436
        if exception is None:
 
437
            exception = sys.exc_info()
 
438
        self.exception = exception
 
439
        self.exception_raise = self._exception_raise
 
440
 
 
441
    def exception_clear(self):
 
442
        self.exception = (None, None, None)
 
443
        self.exception_raise = self._no_exception_to_raise
 
444
 
 
445
    def _no_exception_to_raise(self):
 
446
        pass
 
447
 
 
448
    def _exception_raise(self):
 
449
        """Raise a pending exception that was recorded while
 
450
        getting a Task ready for execution."""
 
451
        exc_type, exc_value = self.exception[:2]
 
452
        raise exc_type, exc_value